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:

  1. Encapsulation violation - Internal representation leaks into external classes. Any refactoring of the editor's internals breaks the history manager.
  2. Tight coupling - The history manager must know the exact structure of the editor's state.
  3. Security risk - Sensitive internal state becomes publicly accessible.
  4. 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 UseWhen NOT to Use
You need undo/redo functionalityState is trivially small and can be copied inline
Object state must be saved and restored without exposing internalsThe 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 processesMemory is extremely constrained and snapshots are large
You want to decouple state-saving logic from the object's core logicYou 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 systemsReal-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

AspectWide InterfaceNarrow Interface
Visible toOriginator onlyCaretaker and other objects
ExposesFull internal stateNothing (or metadata only)
PurposeEnable state restorationEnable storage without access
ImplementationFriend class, inner class, closuresPublic 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:

AspectMemento (State-Based)Command (Operation-Based)
Undo mechanismRestore entire previous state snapshotExecute inverse operation
What is storedComplete object state at a point in timeOperation + parameters (and inverse)
Memory costHigh (full state per snapshot)Low (only operation deltas)
ComplexitySimple - just save and restoreComplex - must define inverse for every operation
CorrectnessAlways correct (exact state restored)Can be tricky (inverse must be perfectly symmetric)
Partial undoDifficult (all-or-nothing per snapshot)Natural (undo individual operations)
EncapsulationPreserved (memento is opaque)May require internal knowledge for inverse
Best forComplex state, few snapshotsSimple operations, many undoable steps
ExamplePhotoshop history snapshotGit revert (inverse commit)
ComposabilityLowHigh (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

AdvantagesDisadvantages
Preserves encapsulation - internal state stays privateHigh memory consumption if state is large or snapshots are frequent
Simplifies the originator - no need to expose internals for undoPerformance 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 levelsIn languages without access control, encapsulation is by convention only
Supports serialization for persistent snapshotsStale 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:

PatternIntentKey MechanismParticipantsTypical Use Case
IteratorSequential access to collection elements without exposing underlying structureExternal cursor traversing a containerIterator, Aggregate, ConcreteIteratorTraversing lists, trees, graphs uniformly
ObserverOne-to-many dependency; when one object changes, all dependents are notifiedSubject maintains subscriber list, broadcasts eventsSubject, Observer, ConcreteSubject, ConcreteObserverEvent systems, UI data binding, pub/sub
StrategyDefine a family of algorithms, encapsulate each, make them interchangeableDelegate algorithm to a composed strategy objectContext, Strategy, ConcreteStrategySorting algorithms, payment methods, compression
CommandEncapsulate a request as an object, enabling parameterization and queuingRequest wrapped in an object with execute/undoCommand, Invoker, Receiver, ConcreteCommandUndo/redo, task queues, macro recording
Template MethodDefine algorithm skeleton in base class; subclasses override specific stepsInheritance with hook methodsAbstractClass, ConcreteClassFrameworks, lifecycle hooks, ETL pipelines
StateObject alters behaviour when internal state changes (appears to change class)Delegate behaviour to current state objectContext, State, ConcreteStateFinite state machines, UI modes, TCP connections
Chain of ResponsibilityPass request along a chain of handlers until one handles itLinked list of handlers, each decides to process or forwardHandler, ConcreteHandler, ClientMiddleware pipelines, event bubbling, logging
VisitorAdd new operations to object structures without modifying themDouble dispatch - element accepts visitorVisitor, ConcreteVisitor, Element, ObjectStructureAST processing, report generation, serialization
MediatorReduce chaotic many-to-many dependencies by centralizing communicationCentral mediator object coordinates colleaguesMediator, Colleague, ConcreteMediatorChat rooms, air traffic control, form validation
MementoCapture and restore object state without violating encapsulationOpaque snapshot token created by originatorOriginator, Memento, CaretakerUndo/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

  1. "What is the expected frequency of state captures?" - Determines whether full snapshots or incremental deltas are appropriate. High frequency demands lightweight mementos.

  2. "Does the undo need to survive application restarts?" - Determines whether mementos need serialization, versioning, and persistent storage.

  3. "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.

  4. "Is this single-user or collaborative?" - Multi-user undo is fundamentally different (per-user stacks, conflict resolution, causal ordering).

  5. "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

  1. 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.

  2. 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.

  3. 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.

  4. 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.

  5. Robert C. Martin - Agile Software Development (2002), Chapter on Command and Memento patterns in undo architectures.

  6. Microsoft Documentation - Implementing Undo with the Memento Pattern in .NET application architecture guidance.