Skip to content

Memento Pattern

The problem

A text editor needs undo. The naive approach stores the entire document string in a list on every keystroke. That works until you realize the document also has cursor position, selection range, scroll offset, and formatting state. Now every undo operation needs to restore all of that together, not just the text. You could expose setters for each field and let the undo manager coordinate them, but that forces the manager to know the editor’s internals. Any refactor of the editor’s internal representation breaks the undo manager too.

Memento solves this by letting the Editor (Originator) package its own state into an opaque Snapshot (Memento) object. The History stack (Caretaker) stores those snapshots and hands them back on undo. The Caretaker never reads the snapshot’s contents. Only the Editor knows how to create a snapshot and how to restore from one. Encapsulation stays intact on both sides.

Structure

classDiagram
class Editor {
-content: string
-cursorPos: number
+type(text)
+moveCursor(pos)
+save() Snapshot
+restore(snapshot)
}
class Snapshot {
-content: string
-cursorPos: number
+getContent() string
+getCursorPos() number
}
class History {
-stack: Snapshot[]
+push(snapshot)
+pop() Snapshot
}
Editor ..> Snapshot : creates
History o-- Snapshot : holds reference only

The key relationship: History holds a reference to Snapshot but never calls getContent or getCursorPos. It only stores and returns the opaque object. The Editor is the sole interpreter of snapshot data.

When to use

  • You need undo/redo and the object’s state is complex or encapsulated.
  • You want to take a point-in-time snapshot of an object without coupling the snapshot holder to the object’s internals.
  • A transaction needs to roll back to a known-good state on failure.
  • You are implementing checkpoints in a long-running process (game saves, wizard steps, form drafts).

Implementation

In TypeScript, Editor returns an opaque snapshot object and stores the real state in a private WeakMap. Code outside Editor can hold the snapshot handle, but it cannot inspect the captured state. Python uses a dataclass with single-underscore-prefixed fields as a convention signal. The Caretaker treats the snapshot as opaque by practice rather than compiler enforcement. In Go, the snapshot struct uses unexported fields, so only code in the same package can construct or read one. External Caretaker code can store and return the pointer but cannot inspect the state inside.

idle
Click Run TS to execute. First run downloads Babel (~400 KB, cached after that).

Tradeoffs

ProCon
Undo/redo without exposing internal stateFrequent snapshots can consume significant memory
Originator controls what gets capturedCaretaker must manage snapshot lifecycle (eviction, limits)
Simple Caretaker: no domain logic, just a stackDeep object graphs are hard to snapshot correctly
Rollback is atomic: restore replaces all state at onceLanguages without true access control rely on convention for encapsulation

Gotchas

  • Snapshots capture state by value. If the Originator holds references to mutable objects (collections, nested objects), a shallow copy produces a snapshot that drifts when the original mutates. Deep-copy or serialize the state explicitly.
  • Caretaker memory grows unbounded without a cap. In production, limit the history depth or use a ring buffer. Text editors typically cap undo at 100-200 steps.
  • Memento and serialization solve overlapping problems. If you already serialize the Originator to JSON for persistence, that JSON can double as the snapshot. Avoid maintaining two snapshot formats.
  • In Go, because snapshot is unexported but lives in the same package as Editor, the package boundary is the encapsulation unit. Splitting Editor and History into separate packages forces you to export the snapshot type, which weakens the guarantee. Design your package structure first.
  • A snapshot from a stale schema causes restore failures after code changes. If you persist snapshots across sessions (game saves, drafts), version the snapshot format and handle migrations.

References

  • Design Patterns, the full GoF catalog
  • Command, Command often uses Memento to implement undo by storing snapshots alongside each command
  • State, State and Memento both deal with an object’s internal condition, but State controls behavior while Memento preserves history