Memento Pattern
1. What the memento pattern is
The memento pattern captures an object’s internal state in a separate object so that state can be restored later, without anything else being able to read it. The captured object is opaque: the code holding it can store it and hand it back, but cannot inspect or modify it.
It has three roles:
- Originator — the object whose state you want to save and restore. It produces mementos and consumes them.
- Memento — an opaque snapshot of the originator’s state at one moment. It exposes a wide interface to the originator (read everything) and a narrow interface to everyone else (read nothing).
- Caretaker — holds mementos and decides when to save or restore. It never looks inside one.
The defining constraint is that narrow/wide split. Plenty of designs can copy an object’s fields somewhere and copy them back; what makes this the memento pattern is that the snapshot stays sealed to everyone except the object that created it. Encapsulation survives the round trip intact.
2. Why snapshot state instead of exposing fields
Snapshotting state through the originator keeps its internal representation sealed; exposing fields to do the same job leaks that representation into every caller. The naive way to support undo is to expose the originator’s fields, copy them out before a change, and copy them back to reverse it. That works until the originator’s internals change shape. Every caller that copied those fields is now a place you have to find and edit.
The memento pattern keeps that representation sealed. The caretaker holds a snapshot it cannot decode, so it cannot depend on what the snapshot contains. When the originator adds a field, changes a type, or restructures its state, the memento changes with it and no caretaker code moves. The caretaker only ever knew “here is a token, give it back to me later.”
That is the whole trade. You give up the ability to inspect saved state from outside the originator, and in return the originator stays free to change its internals without breaking the code that stores its history. For an undo stack, a checkpoint system or a transaction rollback, that is the right side of the trade: the caretaker has no business reading the state anyway.
3. Writing one in Java
The example is a text editor, the same one as in the command pattern post so the two are easy to compare. The editor holds content and a cursor position; we want to snapshot both and roll back to a snapshot later.
3.1. The originator and the opaque memento
The encapsulation guarantee comes from one Java idiom: a public marker interface for the narrow view, and a private nested type for the wide view.
public class TextEditor {
private final StringBuilder content = new StringBuilder();
private int cursor = 0;
/** Narrow interface — all the caretaker is allowed to see. */
public interface Snapshot {
}
/** Wide interface — only TextEditor can read these fields. */
private record State(String content, int cursor) implements Snapshot {
}
public Snapshot save() {
return new State(content.toString(), cursor);
}
public void restore(Snapshot snapshot) {
State state = (State) snapshot;
content.setLength(0);
content.append(state.content());
cursor = state.cursor();
}
}Snapshot is public and empty. The caretaker can hold a Snapshot, pass it around and put it in a list, and that is all the type lets it do. State is a private record, so it is invisible outside TextEditor. The cast (State) snapshot inside restore compiles and runs because State is in scope there and nowhere else. A caretaker that tried the same cast would not compile, because the name State does not exist for it.
The record choice for the wide type is load-bearing. The memento has to be immutable, otherwise the caretaker could mutate a snapshot it is holding and corrupt the history. A record gives you final fields and a value constructor for free. I took the editor’s content as a String rather than sharing the StringBuilder, so the snapshot is a genuine copy and not a live view of the editor’s buffer. §5 returns to why that copy is not always so cheap.
The rest of the editor is ordinary mutation, and knows nothing about saving:
public void type(String text) {
content.insert(cursor, text);
cursor += text.length();
}
public void moveCursor(int position) {
cursor = Math.max(0, Math.min(position, content.length()));
}
public String content() {
return content.toString();
}3.2. The caretaker
The caretaker is a plain stack of snapshots. It stores and returns them; it never opens one.
import java.util.ArrayDeque;
import java.util.Deque;
public class History {
private final Deque<TextEditor.Snapshot> snapshots = new ArrayDeque<>();
public void push(TextEditor.Snapshot snapshot) {
snapshots.push(snapshot);
}
public TextEditor.Snapshot pop() {
return snapshots.pop();
}
public boolean isEmpty() {
return snapshots.isEmpty();
}
}History names TextEditor.Snapshot, but that is the narrow interface, which carries no information. It has no compile-time way to reach content or cursor. The type system enforces that the caretaker cannot depend on the originator’s internals.
3.3. Wiring it together
The client drives the three roles. It asks the editor for a snapshot, hands it to the caretaker, and later feeds a snapshot back to the editor to restore it.
TextEditor editor = new TextEditor();
History history = new History();
editor.type("Hello");
history.push(editor.save()); // checkpoint
editor.type(", world");
System.out.println(editor.content()); // Hello, world
if (!history.isEmpty()) {
editor.restore(history.pop()); // roll back to the checkpoint
}
System.out.println(editor.content()); // HelloSaving and restoring both go through save() and restore() on the originator. The snapshot’s contents never touch any other class. Add a third field to the editor tomorrow, put it in the State record, and not a line of History or this client changes.
4. Memento vs command for undo
Both the memento and the command pattern give you undo, and they do it from opposite directions. A command stores the operation and knows how to reverse it. A memento stores the result and reconstructs it. Choosing between them is mostly a question of what is cheap to copy and what is cheap to invert.
| Command | Memento | |
|---|---|---|
| What you store | The operation plus the data to reverse it | A full snapshot of state |
| Undo logic | Per-command undo(), hand-written | None: just reassign saved state |
| Memory per step | Small (arguments only) | Scales with the size of the state |
| Correctness risk | A wrong undo() desynchronizes silently | Trivial: restore is a copy-back |
| Granularity | One operation | Whole originator |
A command is the better fit when the state is large but each operation touches a tiny part of it and is cheaply invertible: inserting five characters costs five characters of undo data, not a copy of the whole document. A memento is the better fit when the reverse operation is hard to write, or impossible. Some operations destroy information — a filter that drops elements, a numeric round — and you cannot invert what you no longer have. A snapshot does not care. It captured the state before the operation ran, so restoring it is correct no matter what the operation did.
The two also compose. A common real design uses commands for fine-grained edits and a memento every N commands or at semantic boundaries (a saved file, a finished paragraph), so undo is cheap step-by-step and a checkpoint exists when the command history gets too deep to trust.
5. Costs and when not to reach for it
The memento pattern’s cost is memory and copying, and it is easy to underestimate. Every snapshot is a full copy of the originator’s state. A stack of them is that copy times the history depth. For a small editor that is nothing; for a large in-memory document model, an undo stack of mementos can dwarf the document itself.
The sharper trap is shallow copying. A memento is only a safe snapshot if it copies everything that can change. If the originator’s state includes a reference to a mutable object and the memento stores that same reference, then mutating the object later also mutates the snapshot, and “restoring” rolls back to state that has been quietly corrupted. The memento has to deep-copy any mutable structure it captures. In the §3 editor this was free, because String and int are immutable and content.toString() already produced an independent copy. State holding a mutable collection or a mutable object graph does not get that for free, and the set of fields that need their own copies are exactly the ones that hurt to copy.
Skip the pattern when:
- The state is one or two immutable fields. Storing them directly is simpler than a memento type, and there is nothing to encapsulate that a field copy leaks.
- Operations are cheaply and reliably invertible. Use a command. Storing a whole-state snapshot per keystroke when the keystroke knows how to undo itself is paying memory for nothing.
- The state is too large to copy at the frequency you would snapshot it. Either snapshot less often (checkpoints, not every change) or reach for a structural-sharing approach where unchanged parts are not recopied.
The memento pattern earns its place when state needs to round-trip through code that has no business reading it: an undo history, a save-and-restore checkpoint, a transaction that might roll back. When you find yourself wanting to copy an object’s state out and copy it back later and the copy should stay sealed in between, that is the memento. Short of that, a field copy is a field copy.