Chapter 05 · Article 30 of 55
Memento Pattern
Intent: Without violating encapsulation, capture and externalize an object's internal state so that the object can be restored to this state later.
Article outline15 sections on this page+
Overview
Intent: Without violating encapsulation, capture and externalize an object's internal state so that the object can be restored to this state later.
Category: Behavioural Design Pattern
Also Known As: Token, Snapshot
The Memento pattern provides the ability to restore an object to its previous state - essentially an "undo" mechanism. The critical distinction from naive approaches (like exposing getters/setters for all fields) is that the pattern achieves state capture without breaking encapsulation. The object itself decides what state to save and how to restore it; external objects never see or manipulate the internal representation directly.
The pattern introduces a dedicated memento object that acts as an opaque snapshot. Only the originator that created it can read its contents, while external caretakers store and pass mementos around without understanding or modifying them.
Problem It Solves
Consider building a text editor. Users expect Ctrl+Z to undo their last action. A naive implementation might expose every internal field - cursor position, selection range, text buffer, formatting state - through public getters so an external "history manager" can save and restore them. This approach has severe drawbacks:
- Encapsulation violation - Internal representation leaks into external classes. Any refactoring of the editor's internals breaks the history manager.
- Tight coupling - The history manager must know the exact structure of the editor's state.
- Security risk - Sensitive internal state becomes publicly accessible.
- Maintenance burden - Adding a new field to the editor requires updating every external class that saves/restores state.
The Memento pattern solves all of these by letting the originator produce an opaque token (the memento) that encapsulates its own state. The caretaker stores these tokens without knowing their contents, and hands them back to the originator when a restore is needed.
Core problems addressed:
- Implementing undo/redo without exposing internals
- Creating snapshots/checkpoints for rollback
- Supporting transactional semantics (commit/rollback)
- Preserving encapsulation while enabling state persistence
When to Use / When NOT to Use
| When to Use | When NOT to Use |
|---|---|
| You need undo/redo functionality | State is trivially small and can be copied inline |
| Object state must be saved and restored without exposing internals | The object has no meaningful encapsulation to protect |
| You need transactional rollback (save → try → rollback on failure) | State changes are easily reversible via inverse operations (use Command instead) |
| Creating checkpoints in long-running processes | Memory is extremely constrained and snapshots are large |
| You want to decouple state-saving logic from the object's core logic | You need to query or inspect the saved state externally |
| Implementing browser-like navigation (back/forward) | The object's state is immutable (no need for snapshots) |
| Game save/load systems | Real-time systems where snapshot creation latency is unacceptable |
Key Concepts & Theory
Participants
Originator - The object whose state needs to be saved. It creates mementos containing a snapshot of its current internal state and can restore itself from a memento.
Memento - A value object that stores the internal state of the originator. It has two interfaces:
- Wide interface (for the Originator): Provides access to all stored state so the originator can restore itself.
- Narrow interface (for the Caretaker): Exposes nothing or only metadata (timestamp, label). The caretaker cannot inspect or modify the stored state.
Caretaker - Responsible for storing mementos and knowing when to capture/restore state, but never what state is captured. Typically maintains a stack or list of mementos.
Encapsulation Preservation
The fundamental invariant: only the originator can read the memento's contents. In languages with access control (like C++ friend classes or Java inner classes), the memento's wide interface is restricted to the originator. In dynamic languages, this is enforced by convention or closures.
Wide vs Narrow Interface
| Aspect | Wide Interface | Narrow Interface |
|---|---|---|
| Visible to | Originator only | Caretaker and other objects |
| Exposes | Full internal state | Nothing (or metadata only) |
| Purpose | Enable state restoration | Enable storage without access |
| Implementation | Friend class, inner class, closures | Public interface with no state accessors |
State Granularity
Mementos can capture:
- Full state - Complete snapshot of all fields (simple but memory-heavy)
- Incremental/delta state - Only changes since last snapshot (complex but memory-efficient)
- Partial state - Subset of fields relevant to undo (requires careful design)
ASCII Class Diagram
┌─────────────────────────────────┐
│ Caretaker │
├─────────────────────────────────┤
│ - history: Stack<Memento> │
├─────────────────────────────────┤
│ + save(memento) │
│ + undo(): Memento │
│ + redo(): Memento │
└──────────────┬──────────────────┘
│ stores
▼
┌─────────────────────────────────┐
│ <<Memento>> │
├─────────────────────────────────┤
│ - state: Object [private] │
│ - timestamp: Date │
├─────────────────────────────────┤
│ + getState(): Object [wide] │
│ + getTimestamp(): Date [narrow] │
└──────────────▲──────────────────┘
│ creates / restores from
│
┌─────────────────────────────────┐
│ Originator │
├─────────────────────────────────┤
│ - internalState: Object │
├─────────────────────────────────┤
│ + createMemento(): Memento │
│ + restore(memento: Memento) │
│ + doSomething() │
└─────────────────────────────────┘
Interaction flow:
Caretaker Originator Memento
│ │ │
│── createMemento()─►│ │
│ │── new Memento() ──►│
│◄── memento ────────│ │
│ │ │
│ [stores memento in history stack] │
│ │ │
│── restore(m) ─────►│ │
│ │── getState() ────►│
│ │◄── state ─────────│
│ │ │
│ [originator state restored] │
Pseudocode Implementation
Example 1: Text Editor with Undo/Redo
// Memento - stores editor snapshot
class EditorMemento:
private content: String
private cursorPosition: Int
private selectionStart: Int
private selectionEnd: Int
private timestamp: DateTime
constructor(content, cursorPos, selStart, selEnd):
this.content = content
this.cursorPosition = cursorPos
this.selectionStart = selStart
this.selectionEnd = selEnd
this.timestamp = DateTime.now()
// Wide interface - only Editor should call this
function getContent(): return this.content
function getCursorPosition(): return this.cursorPosition
function getSelectionStart(): return this.selectionStart
function getSelectionEnd(): return this.selectionEnd
// Narrow interface - anyone can call
function getTimestamp(): return this.timestamp
// Originator - the text editor
class Editor:
private content: String = ""
private cursorPosition: Int = 0
private selectionStart: Int = -1
private selectionEnd: Int = -1
function type(text: String):
content = content.insertAt(cursorPosition, text)
cursorPosition += text.length
function delete(count: Int):
content = content.removeAt(cursorPosition, count)
function select(start: Int, end: Int):
selectionStart = start
selectionEnd = end
function createMemento(): EditorMemento
return new EditorMemento(content, cursorPosition,
selectionStart, selectionEnd)
function restore(memento: EditorMemento):
this.content = memento.getContent()
this.cursorPosition = memento.getCursorPosition()
this.selectionStart = memento.getSelectionStart()
this.selectionEnd = memento.getSelectionEnd()
// Caretaker - manages undo/redo stacks
class History:
private undoStack: Stack<EditorMemento> = []
private redoStack: Stack<EditorMemento> = []
private editor: Editor
constructor(editor: Editor):
this.editor = editor
function save():
undoStack.push(editor.createMemento())
redoStack.clear() // new action invalidates redo history
function undo():
if undoStack.isEmpty(): return
memento = undoStack.pop()
redoStack.push(editor.createMemento()) // save current for redo
editor.restore(memento)
function redo():
if redoStack.isEmpty(): return
memento = redoStack.pop()
undoStack.push(editor.createMemento()) // save current for undo
editor.restore(memento)
// Usage
editor = new Editor()
history = new History(editor)
history.save()
editor.type("Hello ") // content: "Hello "
history.save()
editor.type("World") // content: "Hello World"
history.save()
editor.delete(5) // content: "Hello "
history.undo() // content: "Hello World"
history.undo() // content: "Hello "
history.redo() // content: "Hello World"
Example 2: Game Save System
// Memento
class GameCheckpoint:
private playerHealth: Int
private playerPosition: Vector3
private inventory: List<Item>
private questProgress: Map<String, Bool>
private savedAt: DateTime
constructor(health, position, inventory, quests):
this.playerHealth = health
this.playerPosition = position
this.inventory = inventory.deepCopy()
this.questProgress = quests.deepCopy()
this.savedAt = DateTime.now()
// Wide interface
function getHealth(): return playerHealth
function getPosition(): return playerPosition
function getInventory(): return inventory
function getQuestProgress(): return questProgress
// Narrow interface
function getSavedAt(): return savedAt
function getLabel(): return "Save @ " + savedAt.format()
// Originator
class GameState:
private playerHealth: Int
private playerPosition: Vector3
private inventory: List<Item>
private questProgress: Map<String, Bool>
function takeDamage(amount): playerHealth -= amount
function move(newPos): playerPosition = newPos
function addItem(item): inventory.add(item)
function completeQuest(id): questProgress[id] = true
function saveCheckpoint(): GameCheckpoint
return new GameCheckpoint(playerHealth, playerPosition,
inventory, questProgress)
function loadCheckpoint(checkpoint: GameCheckpoint):
playerHealth = checkpoint.getHealth()
playerPosition = checkpoint.getPosition()
inventory = checkpoint.getInventory()
questProgress = checkpoint.getQuestProgress()
// Caretaker
class SaveManager:
private slots: Map<String, GameCheckpoint> = {}
private autoSaves: Queue<GameCheckpoint>(maxSize: 3)
function saveToSlot(name: String, game: GameState):
slots[name] = game.saveCheckpoint()
function loadFromSlot(name: String, game: GameState):
if name not in slots: throw "No save in slot"
game.loadCheckpoint(slots[name])
function autoSave(game: GameState):
autoSaves.enqueue(game.saveCheckpoint())
function listSaves(): List<String>
return slots.keys().map(k => k + " - " + slots[k].getLabel())
Memento vs Command for Undo
Both patterns can implement undo, but they take fundamentally different approaches:
| Aspect | Memento (State-Based) | Command (Operation-Based) |
|---|---|---|
| Undo mechanism | Restore entire previous state snapshot | Execute inverse operation |
| What is stored | Complete object state at a point in time | Operation + parameters (and inverse) |
| Memory cost | High (full state per snapshot) | Low (only operation deltas) |
| Complexity | Simple - just save and restore | Complex - must define inverse for every operation |
| Correctness | Always correct (exact state restored) | Can be tricky (inverse must be perfectly symmetric) |
| Partial undo | Difficult (all-or-nothing per snapshot) | Natural (undo individual operations) |
| Encapsulation | Preserved (memento is opaque) | May require internal knowledge for inverse |
| Best for | Complex state, few snapshots | Simple operations, many undoable steps |
| Example | Photoshop history snapshot | Git revert (inverse commit) |
| Composability | Low | High (commands compose into macros) |
Hybrid approach: Many real systems combine both - use Command for recent fine-grained undo and Memento for periodic checkpoints. If the command history becomes too long, collapse it into a memento snapshot.
Real-World Examples
1. Text Editor Undo (VS Code, Vim)
Every keystroke or action group creates a memento. The editor maintains an undo stack of document states. VS Code uses a combination of mementos and operational transforms for collaborative editing.
2. Database Transactions - Savepoints
SAVEPOINT before_update; -- create memento
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
-- something goes wrong...
ROLLBACK TO before_update; -- restore memento
Database engines maintain before-images (mementos) of modified rows in undo logs, enabling rollback without exposing internal page structures.
3. Game Save/Load Systems
RPGs like Skyrim or Dark Souls serialize the entire game state (player stats, world state, NPC positions) into save files - essentially serialized mementos persisted to disk.
4. Browser History (Back/Forward)
Each navigation creates a snapshot of the page state (URL, scroll position, form data). The browser's back/forward buttons restore these snapshots - a caretaker managing a doubly-navigable memento list.
5. Virtual Machine Snapshots
VMware/VirtualBox snapshots capture the entire VM state (memory, disk, CPU registers). Restoring a snapshot returns the VM to that exact point - a memento at the infrastructure level.
6. Version Control Systems
Each Git commit is conceptually a memento of the entire repository state. git checkout <hash> restores the working tree to that snapshot.
Advantages & Disadvantages
| Advantages | Disadvantages |
|---|---|
| Preserves encapsulation - internal state stays private | High memory consumption if state is large or snapshots are frequent |
| Simplifies the originator - no need to expose internals for undo | Performance overhead of deep-copying complex object graphs |
| Clean separation of concerns (originator vs caretaker) | Caretaker doesn't know how much state a memento holds (can't optimize) |
| Easy to implement multiple undo levels | In languages without access control, encapsulation is by convention only |
| Supports serialization for persistent snapshots | Stale mementos may reference objects that no longer exist |
| Can be combined with other patterns (Command, Iterator) | Garbage collection complexity - when to discard old mementos? |
| Enables transactional semantics (try/rollback) | Incremental mementos add significant implementation complexity |
Constraints & Edge Cases
Memory Consumption
Frequent full-state snapshots can exhaust memory. Mitigations:
- Limit history depth - Keep only the last N mementos
- Incremental mementos - Store only deltas between states
- Compression - Compress serialized state
- Copy-on-write - Share unchanged portions between snapshots (like Git's tree objects)
Serialization of Mementos
When mementos must persist across sessions (game saves, crash recovery):
- Ensure all state is serializable (no transient references, file handles, sockets)
- Version the memento format for backward compatibility
- Handle deserialization of objects whose classes have changed
Partial State Capture
Deciding what to include in a memento is non-trivial:
- Derived/cached state can be recomputed - exclude it to save space
- References to external objects - store IDs rather than deep copies
- Thread-local or environment-dependent state - cannot be meaningfully restored
Large Object Graphs
When the originator contains deep object hierarchies:
- Deep copy is expensive and error-prone (circular references)
- Consider structural sharing (persistent data structures)
- Use the Prototype pattern for efficient cloning
Concurrency
- Memento creation must be atomic - partial snapshots are corrupted snapshots
- In multi-threaded systems, synchronize state capture or use immutable state
Memento Lifetime Management
- Mementos may hold references that prevent garbage collection
- Implement explicit disposal or weak references for long-lived caretakers
- Time-based or count-based eviction policies
Behavioural Patterns Summary Comparison
Since this is the final article in the behavioural patterns section, here is a comprehensive comparison of all 10 behavioural design patterns:
| Pattern | Intent | Key Mechanism | Participants | Typical Use Case |
|---|---|---|---|---|
| Iterator | Sequential access to collection elements without exposing underlying structure | External cursor traversing a container | Iterator, Aggregate, ConcreteIterator | Traversing lists, trees, graphs uniformly |
| Observer | One-to-many dependency; when one object changes, all dependents are notified | Subject maintains subscriber list, broadcasts events | Subject, Observer, ConcreteSubject, ConcreteObserver | Event systems, UI data binding, pub/sub |
| Strategy | Define a family of algorithms, encapsulate each, make them interchangeable | Delegate algorithm to a composed strategy object | Context, Strategy, ConcreteStrategy | Sorting algorithms, payment methods, compression |
| Command | Encapsulate a request as an object, enabling parameterization and queuing | Request wrapped in an object with execute/undo | Command, Invoker, Receiver, ConcreteCommand | Undo/redo, task queues, macro recording |
| Template Method | Define algorithm skeleton in base class; subclasses override specific steps | Inheritance with hook methods | AbstractClass, ConcreteClass | Frameworks, lifecycle hooks, ETL pipelines |
| State | Object alters behaviour when internal state changes (appears to change class) | Delegate behaviour to current state object | Context, State, ConcreteState | Finite state machines, UI modes, TCP connections |
| Chain of Responsibility | Pass request along a chain of handlers until one handles it | Linked list of handlers, each decides to process or forward | Handler, ConcreteHandler, Client | Middleware pipelines, event bubbling, logging |
| Visitor | Add new operations to object structures without modifying them | Double dispatch - element accepts visitor | Visitor, ConcreteVisitor, Element, ObjectStructure | AST processing, report generation, serialization |
| Mediator | Reduce chaotic many-to-many dependencies by centralizing communication | Central mediator object coordinates colleagues | Mediator, Colleague, ConcreteMediator | Chat rooms, air traffic control, form validation |
| Memento | Capture and restore object state without violating encapsulation | Opaque snapshot token created by originator | Originator, Memento, Caretaker | Undo/redo, game saves, transaction rollback |
Cross-Pattern Relationships
- Command + Memento: Command stores operations for undo; Memento stores state for undo. Often combined - commands for recent history, mementos for checkpoints.
- Iterator + Memento: Iterator can store its position as a memento for bookmarking.
- State + Memento: State transitions can be rolled back using mementos of previous state configurations.
- Observer + Mediator: Both manage communication; Observer is decentralized, Mediator is centralized.
- Strategy + State: Both delegate behaviour; Strategy is chosen externally, State transitions internally.
- Visitor + Iterator: Visitor operates on elements; Iterator provides the traversal mechanism.
Interview Follow-ups
Q1: How would you implement undo with limited memory?
Answer: Use a bounded history with eviction policy (e.g., keep last 50 mementos). For further optimization, store incremental mementos (deltas) rather than full snapshots. Periodically create full snapshots as "keyframes" and store deltas between them - similar to video compression (I-frames and P-frames). To undo, find the nearest keyframe and replay deltas forward to the desired point.
Q2: How does the Memento pattern preserve encapsulation in languages without access modifiers (e.g., Python, JavaScript)?
Answer: Use closures or symbols. In JavaScript, the memento's state can be stored in a closure that only the originator's restore method can access. In Python, prefix with underscore (convention) or use __slots__ with name mangling. Another approach: the originator encrypts/hashes the memento contents so only it can decode them - though this is rarely practical. The key insight is that encapsulation is a design contract, not always a language enforcement.
Q3: When would you serialize mementos to disk vs keep them in memory?
Answer: Serialize to disk when: (1) mementos must survive process restarts (game saves, crash recovery), (2) memory is constrained and history is long, (3) you need audit trails or compliance records. Keep in memory when: (1) undo is session-scoped only, (2) latency of disk I/O is unacceptable, (3) state contains non-serializable resources. Hybrid: keep recent mementos in memory, spill older ones to disk (like an LRU cache with disk backing).
Q4: How would you design a memento system for a collaborative editor where multiple users edit simultaneously?
Hint: Think about operational transforms or CRDTs. Each user's undo should only reverse their operations, not others'. Consider per-user memento stacks combined with a shared document state that uses vector clocks or causal ordering.
Q5: How would you handle memento versioning when the originator's class evolves over time?
Hint: Consider schema evolution strategies - adding default values for new fields, migration functions between memento versions, and storing a version identifier in each memento. Look at how database migration tools (Flyway, Alembic) handle schema changes for inspiration.
Counter Questions to Ask Interviewer
-
"What is the expected frequency of state captures?" - Determines whether full snapshots or incremental deltas are appropriate. High frequency demands lightweight mementos.
-
"Does the undo need to survive application restarts?" - Determines whether mementos need serialization, versioning, and persistent storage.
-
"How large is the object's state, and which parts change most frequently?" - Guides decisions about partial vs full capture, and whether copy-on-write or structural sharing is warranted.
-
"Is this single-user or collaborative?" - Multi-user undo is fundamentally different (per-user stacks, conflict resolution, causal ordering).
-
"Are there operations that should NOT be undoable?" - Some actions (sending an email, charging a credit card) have real-world side effects that mementos cannot reverse. Clarifies system boundaries.
References & Whitepapers
-
Gamma, Helm, Johnson, Vlissides - Design Patterns: Elements of Reusable Object-Oriented Software (1994), Chapter 5: Behavioral Patterns - Memento. The canonical reference defining the pattern's structure and motivation.
-
Martin Fowler - Event Sourcing (2005). Event Sourcing stores every state change as an event - conceptually an append-only log of mementos. Replaying events reconstructs any historical state. Connection: Memento captures state at a point; Event Sourcing captures the transitions between states.
-
Greg Young - CQRS and Event Sourcing (2010). Command Query Responsibility Segregation separates read and write models. The write side uses event sourcing (related to Memento), while the read side projects current state. Snapshots in event-sourced systems are literally mementos used to avoid replaying the entire event log.
-
Pat Helland - Immutability Changes Everything (2015). Argues for append-only data structures where history is never lost - aligning with the Memento philosophy of preserving past states.
-
Robert C. Martin - Agile Software Development (2002), Chapter on Command and Memento patterns in undo architectures.
-
Microsoft Documentation - Implementing Undo with the Memento Pattern in .NET application architecture guidance.
Related Topics
- Iterator Pattern - Traversal over memento collections
- Observer Pattern - Notify on state changes that trigger memento creation
- Strategy Pattern - Different snapshot strategies (full vs incremental)
- Command Pattern - Alternative/complement for undo; often combined with Memento
- Template Method Pattern - Template for save/restore lifecycle
- State Pattern - State transitions that may need rollback via Memento
- Chain of Responsibility Pattern - Pipeline of validators before memento restore
- Visitor Pattern - Visiting memento collections for analysis/reporting
- Mediator Pattern - Coordinating memento operations across multiple originators
- Prototype Pattern - Deep cloning used in memento creation
- Event Sourcing - Append-only state history
- CQRS - Separating read/write with snapshot support