Behavioral

Memento

Capture and externalise an object's internal state so it can be restored later — without violating encapsulation.

Intermediate Pattern #18 of 23

What is the Memento Pattern?

Memento lets you save and restore snapshots of an object's state without exposing its internal structure. The originator creates a memento containing a copy of its private state. The caretaker stores and returns mementos. The originator restores from a memento when needed.

Crucially, only the originator can read the memento's content — the caretaker stores it as an opaque token, preserving encapsulation.

Real-world analogy: Video game save points. When you save, the game writes your entire state (position, health, inventory) to a save file (memento). When you reload, the exact state is restored — the save system (caretaker) never knows the details of the game state.

The Problem It Solves

You need undo in a text editor. The naive approach: make all text editor fields public so the history manager can read and save them. This violates encapsulation — internal state is exposed to the outside world.

Memento solves it: the TextEditor creates a snapshot of its own private state and returns it as an opaque object. The history manager stores it without reading it. On undo, the editor receives its snapshot back and restores itself — no outside code ever sees internal state.

Participants

Originator
Creates mementos capturing its current state. Restores its state from a memento. The only class that reads or writes memento content.
Memento
Stores a snapshot of the Originator's internal state. Exposes its state only to the Originator (via package-private or inner class access). Opaque to everyone else.
Caretaker
Stores and manages mementos (typically in a stack for undo). Never examines or modifies memento content — treats them as opaque tokens.

Visual Flow Diagram

Originator - state (private) + save() → Memento + restore(Memento) only one who reads memento content Memento - state (opaque) getState() — only originator can call opaque to caretaker Caretaker history: Stack <Memento> backup() undo() save() restore() stores Flow: Originator.save() → Memento → Caretaker stores it → on undo: Caretaker returns Memento → Originator.restore(memento)

Java Code Example

Java — Text Editor with Undo History
// Originator — creates and restores from mementos
public class TextEditor {
    private String  text     = "";
    private int     cursorPos = 0;
    private String  selection = null;

    public void type(String chars) {
        text = text.substring(0, cursorPos) + chars + text.substring(cursorPos);
        cursorPos += chars.length();
    }

    // Creates a snapshot — only TextEditor can read it back
    public Snapshot save() {
        return new Snapshot(text, cursorPos, selection); // captures full state
    }

    // Restores from snapshot
    public void restore(Snapshot snapshot) {
        this.text      = snapshot.getText();
        this.cursorPos = snapshot.getCursorPos();
        this.selection = snapshot.getSelection();
    }

    public String getText() { return text; }

    // ── Memento — inner class preserves encapsulation ──────────
    // Only TextEditor (enclosing class) can access getters
    public static class Snapshot {
        private final String  text;
        private final int     cursorPos;
        private final String  selection;
        private final long    timestamp;

        private Snapshot(String text, int cursorPos, String selection) {
            this.text      = text;
            this.cursorPos = cursorPos;
            this.selection = selection;
            this.timestamp = System.currentTimeMillis();
        }

        // Package/private access — only originator uses these
        private String  getText()      { return text; }
        private int     getCursorPos() { return cursorPos; }
        private String  getSelection() { return selection; }
        public long     getTimestamp() { return timestamp; } // ok to expose

        public String toString() {
            return "Snapshot@" + timestamp + " (opaque)"; // caretaker sees this only
        }
    }
}

// Caretaker — manages history, never reads memento content
public class History {
    private final Deque<TextEditor.Snapshot> undoStack = new ArrayDeque<>();
    private final Deque<TextEditor.Snapshot> redoStack = new ArrayDeque<>();
    private final TextEditor editor;

    public History(TextEditor editor) { this.editor = editor; }

    public void backup() {
        undoStack.push(editor.save()); // store snapshot — never reads it
        redoStack.clear();
    }

    public void undo() {
        if (undoStack.isEmpty()) { System.out.println("Nothing to undo"); return; }
        redoStack.push(editor.save());         // save current for redo
        editor.restore(undoStack.pop());         // restore from snapshot
    }

    public void redo() {
        if (redoStack.isEmpty()) { System.out.println("Nothing to redo"); return; }
        undoStack.push(editor.save());
        editor.restore(redoStack.pop());
    }
}

// Client
public class Main {
    public static void main(String[] args) {
        TextEditor editor  = new TextEditor();
        History    history = new History(editor);

        history.backup(); editor.type("Hello");
        history.backup(); editor.type(" World");
        System.out.println(editor.getText()); // Hello World

        history.undo();
        System.out.println(editor.getText()); // Hello

        history.undo();
        System.out.println(editor.getText()); // (empty)

        history.redo();
        System.out.println(editor.getText()); // Hello
    }
}

When to Use / Avoid

✓ Use When

  • You need undo/redo functionality without exposing object internals
  • A snapshot of an object's state is needed to restore it later
  • Direct access to the object's fields would expose its implementation
  • Transactional operations that need rollback support

✕ Avoid When

  • State snapshots are large and memory is constrained
  • Frequent saves create performance overhead
  • The state contains non-serialisable references (open streams, live connections)

Real-World Examples

Pros & Cons

Pros

  • Preserves encapsulation — caretaker never sees internal state
  • Simplifies the originator — undo/redo logic lives in caretaker
  • Clean separation: originator creates/restores, caretaker manages lifecycle

Cons

  • Memory intensive — deep history stacks can consume large amounts of RAM
  • Serialising complex object graphs (circular refs, open resources) is tricky
  • Caretaker must know when to save — missed saves lose history

How Memento Can Be Broken

⚠ Attack Vectors

  • Caretaker reading memento internals: If the Memento has public getters for all state fields, the caretaker (or any class) can read and potentially modify the snapshot — encapsulation is broken
  • Shallow snapshot of mutable state: Memento stores a reference to a mutable nested object — after saving, the originator mutates that object, corrupting the snapshot retroactively
  • Unbounded history stack: Every keystroke creates a snapshot — after millions of edits the history consumes all available heap memory
  • Restoring stale snapshot with live resources: Snapshot contains a reference to an open DB connection or file handle — restoring it after those resources have been closed causes errors

✓ Prevention

  • Restrict memento access with inner class or package-private: Make memento state fields private and getters accessible only to the originator — use a private static inner class or narrow package visibility
  • Deep copy all mutable state: Every mutable object stored in the snapshot must be copied at snapshot time — never store live references to the originator's mutable fields
  • Bounded history with LRU or max size: Cap the undo stack at a sensible maximum (e.g., 50 steps). Use an ArrayDeque with a size check and evict the oldest snapshot when full
  • Exclude non-serialisable resources: Never include open streams, connections, or thread references in a snapshot — store only value data that can be safely recreated
Java — Break & Fix
// ❌ BREAKING — shallow copy, originator corrupts snapshot
public class BadSnapshot {
    public final List<String> items; // ❌ reference to live mutable list
    public BadSnapshot(List<String> items) { this.items = items; }
}
// After save: originator.items.add("new") → snapshot is corrupted too!

// ✅ FIX — deep copy at snapshot time
public class GoodSnapshot {
    private final List<String> items;
    public GoodSnapshot(List<String> items) {
        this.items = List.copyOf(items); // ✅ immutable defensive copy
    }
}

// ❌ BREAKING — public getters expose internals to caretaker
public class LeakySnapshot {
    public String getText()      { return text; }      // ❌ anyone can read
    public int    getCursorPos()  { return cursorPos; } // ❌ internals exposed
}

// ✅ FIX — private static inner class, only outer class accesses
public class TextEditor {
    private String text;

    public static class Snapshot {
        private final String text; // private — caretaker cannot read
        private Snapshot(String t) { this.text = t; }
        private String getText() { return text; } // only TextEditor calls this
    }

    public Snapshot save()                  { return new Snapshot(text); }
    public void     restore(Snapshot s)      { this.text = s.getText(); }
}

// ✅ Bounded history — evict oldest when full
private static final int MAX_HISTORY = 50;

public void backup() {
    if (undoStack.size() >= MAX_HISTORY) {
        undoStack.removeLast(); // evict oldest to cap memory
    }
    undoStack.push(editor.save());
}

How Other Patterns Relate

Interview Cheat Sheet

  1. What: Saves and restores an object's private state without breaking encapsulation. Originator creates/restores snapshots. Caretaker stores them as opaque tokens — never reads their content.
  2. How: Originator has save() → Snapshot and restore(Snapshot). Snapshot is a private inner class with private fields/getters — only the enclosing Originator can access them. Caretaker holds a Stack<Snapshot>.
  3. Critical rules: Always deep-copy mutable fields into the snapshot. Cap history stack size to prevent memory leaks. Use Command + Memento together for a complete undo/redo system.