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.
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
// 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 aMementoof 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
// 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());
}
}
}// 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 snapshotNotice 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 state | State 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 itself | The 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 invariants | Full 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:
| Memento | Command | |
|---|---|---|
| What it stores | A snapshot of state at a point in time | An action (and usually its inverse action) |
| How undo works | Restore the old snapshot wholesale | Execute the inverse operation (insert undone by delete) |
| Cost | Proportional to the size of the captured state | Proportional to the size of the operation, often much cheaper |
| Best fit | State 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 internals | Tight — only the Originator can create/consume its own Memento | Looser — a Command just needs enough info to redo/undo its specific action |
// 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 changedA 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 savepoints —
SAVEPOINT/ROLLBACK TO SAVEPOINTin 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
HEADacting 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
@Transactionalrollback — 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
EditorMementoin the example above thread-safe to share across multipleEditorinstances? Why or why not? - Where does Memento show up in database systems, and how is a
SAVEPOINTconceptually similar to (and different from) a textbook object Memento?