04-behavioral-patterns

Memento Pattern: Capturing and Restoring Object State

How to implement undo and snapshot functionality by capturing an object's internal state externally, without breaking its encapsulation.

August 11, 2026
lldbehavioralmementoundoiteratorvisitordouble-dispatch

Memento Pattern

"Without violating encapsulation, capture and externalize an object's internal state so that the object can be restored to this state later." Undo buttons, snapshot/rollback features, and "save game" functionality all need the same thing: a way to remember what an object looked like at some point in time and put it back. The naive approach either exposes every private field with public setters (breaking encapsulation for everyone, not just the undo feature) or duplicates the object's fields into some external "history" structure that has to be kept manually in sync. The Memento pattern gives the object itself sole responsibility for producing and consuming its own snapshots, while an external caretaker just stores them opaquely.

This guide is one of three covering collection- and state-capture-adjacent patterns in this phase — see also Iterator Pattern (traversal) and Visitor Pattern (operations over structure).


1. The Problem: Breaking Encapsulation to Enable Undo

java
// VIOLATION: Editor exposes every internal field via setters purely so an
// external UndoHistory can capture and restore state.
class Editor {
    private String text = "";
    private int cursorPosition = 0;
    private String fontFamily = "Default";
 
    public String getText() { return text; }
    public void setText(String text) { this.text = text; }
    public int getCursorPosition() { return cursorPosition; }
    public void setCursorPosition(int pos) { this.cursorPosition = pos; }
    public String getFontFamily() { return fontFamily; }
    public void setFontFamily(String font) { this.fontFamily = font; } // exposed just for undo
 
    void type(String input) {
        text += input;
        cursorPosition += input.length();
    }
}
 
// External history has to know EVERY field and copy them manually
class UndoHistory {
    private final Deque<Map<String, Object>> snapshots = new ArrayDeque<>();
 
    void save(Editor editor) {
        Map<String, Object> snapshot = new HashMap<>();
        snapshot.put("text", editor.getText());
        snapshot.put("cursorPosition", editor.getCursorPosition());
        snapshot.put("fontFamily", editor.getFontFamily());
        snapshots.push(snapshot);
    }
 
    void undo(Editor editor) {
        Map<String, Object> snapshot = snapshots.pop();
        editor.setText((String) snapshot.get("text"));
        editor.setCursorPosition((Integer) snapshot.get("cursorPosition"));
        editor.setFontFamily((String) snapshot.get("fontFamily"));
    }
}

Two problems compound here. First, setFontFamily() and friends now exist as public API purely to support undo — any other code in the codebase can call them too, and Editor's real invariants (e.g. "cursor position must never exceed text length") can be violated by any caller, not just the undo mechanism. Second, UndoHistory has to be updated every time Editor gains a new field, or a restore silently loses data.

⚠️

The signal: a class exposes setters for fields that no normal client actually needs to set — only an undo/history/snapshot mechanism does. That's a sign the setter shouldn't be public at all; it should be a private detail the class uses to restore itself from its own memento.


2. Structure

  • Originator (Editor) — the object whose state is being captured. It alone knows how to produce a Memento of itself and how to restore from one.
  • Memento (EditorMemento) — an immutable snapshot. It exposes a narrow interface to everyone except the Originator (often nothing at all — a marker interface) and a wide interface only the Originator can see (its actual fields), typically achieved with a private inner class.
  • Caretaker (UndoHistory) — stores mementos (e.g. in a stack) but never inspects or modifies their contents. It only knows "save this" and "give me the last one back."

3. Full Java Implementation

java
// Originator — owns both state and the ability to snapshot/restore it
class Editor {
    private String text = "";
    private int cursorPosition = 0;
    private String fontFamily = "Default";
 
    void type(String input) {
        text += input;
        cursorPosition += input.length();
    }
 
    void setFont(String font) {
        this.fontFamily = font;
    }
 
    String getText() { return text; } // legitimate read-only accessor for normal clients
 
    // Produces an opaque snapshot — no setter on Editor is ever public
    EditorMemento save() {
        return new EditorMemento(text, cursorPosition, fontFamily);
    }
 
    // Only the Editor knows how to consume its own memento
    void restore(EditorMemento memento) {
        this.text = memento.text;
        this.cursorPosition = memento.cursorPosition;
        this.fontFamily = memento.fontFamily;
    }
 
    // Memento is a static nested class: it can only be constructed and read
    // by Editor itself (package-private fields, no public accessors), so the
    // Caretaker holds a reference it fundamentally cannot inspect or mutate.
    static class EditorMemento {
        private final String text;
        private final int cursorPosition;
        private final String fontFamily;
 
        private EditorMemento(String text, int cursorPosition, String fontFamily) {
            this.text = text;
            this.cursorPosition = cursorPosition;
            this.fontFamily = fontFamily;
        }
    }
}
 
// Caretaker — stores mementos opaquely, never reaches inside one
class UndoHistory {
    private final Deque<Editor.EditorMemento> history = new ArrayDeque<>();
 
    void save(Editor editor) {
        history.push(editor.save());
    }
 
    void undo(Editor editor) {
        if (!history.isEmpty()) {
            editor.restore(history.pop());
        }
    }
}
java
// Usage
Editor editor = new Editor();
UndoHistory undoHistory = new UndoHistory();
 
undoHistory.save(editor);      // snapshot: ""
editor.type("Hello");
undoHistory.save(editor);      // snapshot: "Hello"
editor.type(" World");
System.out.println(editor.getText()); // "Hello World"
 
undoHistory.undo(editor);
System.out.println(editor.getText()); // "Hello" — restored via the Originator's own restore()
 
undoHistory.undo(editor);
System.out.println(editor.getText()); // "" — back to the initial snapshot

Notice Editor never grew a public setText(), setCursorPosition(), or setFontFamily(). UndoHistory cannot construct, read, or corrupt an EditorMemento — Java's private visibility inside the nested class enforces that the memento's contents are only ever touched by Editor itself, which is what "capture state without violating encapsulation" means concretely.

In languages without nested-class privacy tricks, the same effect is achieved with a Memento marker interface that exposes zero methods to the Caretaker, while the Originator internally casts back to a concrete type it alone defines. The principle is the same: two access levels for the same object, enforced by the type system.

⚠️

Cost to watch: mementos that capture large object graphs (a whole document, a whole game world) can make an undo stack expensive in memory. Common mitigations: store only the diff from the previous state, cap history depth, or use a Command-based approach instead (see the comparison below) when snapshotting is too heavyweight.


4. When to Use vs. When It's Overkill

Use Memento when...It's overkill when...
You need undo/redo, checkpoints, or rollback of an object's stateState is trivial (one or two primitive fields) — just store the old value directly
The object's invariants make ad-hoc external mutation dangerous, and you want restoration to go through the object itselfThe object is already immutable — there's nothing to restore, previous instances are the "snapshots"
You want to snapshot without exposing setters that would let arbitrary code corrupt invariantsFull state snapshots would be prohibitively large/expensive relative to how often undo is used
Multiple checkpoints need to be kept (a full undo stack, not just "one step back")You only ever need to reverse the last discrete operation, and that operation's inverse is easy to express directly (favor Command instead)

5. Memento vs. Command

Both patterns are the backbone of undo functionality, and real editors often use them together — but they capture fundamentally different things:

MementoCommand
What it storesA snapshot of state at a point in timeAn action (and usually its inverse action)
How undo worksRestore the old snapshot wholesaleExecute the inverse operation (insert undone by delete)
CostProportional to the size of the captured stateProportional to the size of the operation, often much cheaper
Best fitState is complex/interrelated and hard to invert correctly (e.g. reformatting a whole document)Individual operations are small and cleanly invertible (e.g. "typed 5 characters" undone by "delete 5 characters")
Coupling to Originator internalsTight — only the Originator can create/consume its own MementoLooser — a Command just needs enough info to redo/undo its specific action
java
// Command-based undo: store the action and its inverse, not a full snapshot
class TypeCommand {
    private final Editor editor;
    private final String insertedText;
 
    TypeCommand(Editor editor, String text) {
        this.editor = editor;
        this.insertedText = text;
    }
 
    void execute() { editor.type(insertedText); }
    void undo() { editor.deleteLast(insertedText.length()); } // cheap, targeted inverse
}
 
// Memento-based undo: store the whole state, regardless of what changed
undoHistory.save(editor); // captures text + cursor + font, even if only text changed

A practical rule of thumb: reach for Command when operations are small and naturally invertible (text editors typically use Command per keystroke/operation for cheap, granular undo). Reach for Memento when the state is too tangled to invert reliably, or when you need "restore to exactly this point," not just "reverse the last N actions" — e.g. game save states, database checkpoint/rollback, form-wizard "back" buttons that must restore an entire multi-field snapshot.


6. Real-World Examples

  • Text/code editor undo stacks — most production editors use a hybrid: Command for cheap per-keystroke undo, occasionally checkpointed with a Memento-style full snapshot to bound how far commands must be replayed.
  • Database transaction savepointsSAVEPOINT / ROLLBACK TO SAVEPOINT in SQL is Memento at the database engine level: a snapshot of transaction state that can be restored without breaking the DB's own invariants.
  • Version control snapshots — Git commits are effectively immutable mementos of the working tree's full state, with HEAD acting as the Originator's reference to "the current one."
  • Game save states — serializing an entire game world's state to allow "load game" is Memento at the scale of an entire object graph.
  • Form wizards — multi-step forms that let users go "Back" without losing previously entered data on other steps snapshot each step's Originator state as the user progresses.
  • Spring's @Transactional rollback — conceptually similar: the framework captures enough state (or defers commits) to "restore" to a pre-transaction state on exception, though the mechanism (transaction log, not object snapshot) differs from a textbook Memento.

Interview Questions

  • What does "capture state without violating encapsulation" mean concretely, and how does the Memento pattern's use of a private nested class (or marker interface) achieve it in Java?
  • Walk through the three participants of Memento — Originator, Memento, Caretaker — and state exactly what each one is and isn't allowed to do with the memento's contents.
  • Why shouldn't a class add public setters for every field just to support an undo feature? What's the actual risk?
  • Compare Memento and Command for implementing undo. When would you choose one over the other, and could a real system use both together?
  • What's the memory cost concern with Memento at scale, and what are two ways to mitigate it?
  • How would you implement redo (not just undo) on top of a Memento-based undo stack?
  • Is EditorMemento in the example above thread-safe to share across multiple Editor instances? Why or why not?
  • Where does Memento show up in database systems, and how is a SAVEPOINT conceptually similar to (and different from) a textbook object Memento?