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 onlyThe 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.
Click Run TS to execute. First run downloads Babel (~400 KB, cached after that).
Click Run Python to execute. First run downloads Python (~10 MB, cached after that).
Click Run Go to execute. Runs via the Go Playground API.
Tradeoffs
| Pro | Con |
|---|---|
| Undo/redo without exposing internal state | Frequent snapshots can consume significant memory |
| Originator controls what gets captured | Caretaker must manage snapshot lifecycle (eviction, limits) |
| Simple Caretaker: no domain logic, just a stack | Deep object graphs are hard to snapshot correctly |
| Rollback is atomic: restore replaces all state at once | Languages 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
snapshotis unexported but lives in the same package asEditor, 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: Memento, GoF, the canonical definition and known uses
- Refactoring Guru: Memento, diagrams and multiple-language examples
- SourceMaking: Memento, additional context and known uses
Related topics
- 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