Memento Pattern
Memento powers undo, snapshotting, checkpointing, and save-game.
Introduction
Memento powers undo, snapshotting, checkpointing, and save-game. Originator creates Memento with state snapshot; later restore(memento). Caretaker holds Mementos but cannot read them — preserving encapsulation. Variants: Redux immutable snapshots, DB savepoints, Command undo stacks.
The story
A spreadsheet undo was tangled per-command hacks, frequently buggy. Memento per command grabbing copy of changed cells made undo bulletproof and enabled multi-step undo, redo, and crash recovery.
The business problem
Ad-hoc undo and snapshot logic breaks and leaks encapsulation:
- Buggy undo: per-operation hacks miss edge cases.
- Full snapshot cost: saving entire document every keystroke too expensive.
- Encapsulation break: caretaker reading internal state directly.
- No redo/crash recovery: single-level undo insufficient for editors.
The problem teams faced
Memento fits when:
- Undo/redo needs exact state restoration at right granularity.
- Originator must hide internal representation from caretaker.
- Snapshots stored externally (stack, disk, other process).
- Paired with Command for reversible operations.
Understanding the topic
Intent: Capture state without violating encapsulation; restore later.
- Originator — createMemento() and restore(memento).
- Memento — opaque snapshot; only Originator reads contents.
- Caretaker — stores Mementos (undo stack); never inspects inside.
Internal architecture
Spreadsheet cell memento:
class Spreadsheet {private cells = new Map<string, string>()createMemento(changedKeys: string[]): SpreadsheetMemento {const snapshot = new Map<string, string>()for (const key of changedKeys) snapshot.set(key, this.cells.get(key) ?? "")return new SpreadsheetMemento(snapshot) // opaque to caretaker}restore(m: SpreadsheetMemento) {for (const [key, val] of m.getCells()) this.cells.set(key, val)}}class UndoStack {private stack: SpreadsheetMemento[] = []push(m: SpreadsheetMemento) { this.stack.push(m) }pop(): SpreadsheetMemento | undefined { return this.stack.pop() }}
Visual explanation
Three diagrams cover memento flow, undo stack, and vs Command:
Informative example
Before / after — ad-hoc undo to Memento:
// ❌ Ad-hoc undo — misses cells, breaks on mergelet lastCells: Map<string, string> | null = nullfunction editCell(key: string, val: string) {lastCells = new Map(cells) // full copy every time — slowcells.set(key, val)}function undo() { if (lastCells) cells = lastCells }// ✅ Memento per command — changed cells onlyclass EditCellCommand {private memento?: SpreadsheetMementoexecute(sheet: Spreadsheet, key: string, val: string) {this.memento = sheet.createMemento([key])sheet.setCell(key, val)}undo(sheet: Spreadsheet) {if (this.memento) sheet.restore(this.memento)}}
Execution workflow
Before mutate
Originator.createMemento(changedScope).
Real-world use
Text editor undo stacks, Git stash/commits as snapshots, DB SAVEPOINT, Redux state history, game save slots, VM checkpoints.
Production case study
Spreadsheet undo refactor:
- Scenario: Per-command undo hacks buggy across 40 operations.
- Decision: Memento per command — snapshot changed cells only.
- Outcome: Bulletproof undo/redo; crash recovery from persisted stack.
Trade-offs
- Pro: encapsulation preserved; exact restore; granular snapshots.
- Pro: caretaker simple — stack storage only.
- Con: memory cost for many snapshots — limit stack depth.
- Con: deep copies expensive — optimize granularity.
Decision framework
- Use with Command for undo/redo systems.
- Use when originator internals must stay hidden from history store.
- Avoid full-document snapshot on every keystroke — scope memento.
- Limit undo stack depth in memory-constrained clients.
Best practices
- Memento opaque to caretaker — only Originator reads/writes snapshot.
- Snapshot minimal changed state — not whole aggregate.
- Persist memento stack for crash recovery when needed.
Anti-patterns to avoid
- Caretaker casting memento to read internals — breaks encapsulation.
- Unbounded undo stack on large documents — memory leak class.
- Memento storing non-serializable handles (sockets).
Common mistakes
- Shallow copy in memento — nested mutation aliases live state.
- Restore without validating memento version/schema.
- Undo stack not cleared on irreversible external side effect.
Debugging tips
- Compare cell count in memento vs expected changed scope.
- Replay undo stack in test to reproduce user report.
Optimization strategies
- Structural sharing (immutable data) reduces memento copy cost — Redux model.
- Limit stack to N levels; drop oldest.
Common misconceptions
- Memento ≠ Prototype. Memento captures for restore; Prototype clones for reuse template.
- Command undo often uses Memento internally for state capture.
- Git commit is memento-like snapshot with DAG history.
Advanced interview questions
Interview Prep
Practice concise answers, then expand each card for the explanation.
1IntermediateQuestionMemento vs Command?+
Answer
Follow-up
2AdvancedQuestionWhy opaque memento?+
Answer
Follow-up
3IntermediateQuestionMemento vs Prototype?+
Answer
Follow-up
4AdvancedQuestionGranularity trade-off?+
Answer
Follow-up
Summary
You can implement undo stacks with Memento, preserve encapsulation, and pick snapshot granularity. Tell the spreadsheet undo refactor story — if you can do that, you own Memento.