intermediate~2h

Memento Pattern

Learn how to add undo functionality to an object without exposing its internal state, by splitting the work across an Originator, an opaque Memento snapshot, and a Caretaker that manages history.

Learning objectives

  • Explain why bolting undo history directly onto an editable class violates single responsibility
  • Split state-saving responsibility across an Originator, a Memento, and a Caretaker
  • Implement an immutable snapshot class using a final field and a restricted getter
  • Recognize and fix the shallow-copy trap when a memento's fields are mutable
  • Identify where checkpoint-and-restore designs appear in real editors, games, and databases

◆ Story

Think about a save point in a video game. When you save, the game captures your character's health, position, inventory, and quest progress into a single file. You never see the raw variables that make up that save — you just know that loading it later puts everything back exactly where it was. And you can't reach into the save file and hand-edit individual numbers; the only way in or out is through the game's own save and load menus.

That is the exact shape of the problem this pattern solves: capture an object's state at a point in time, store it somewhere safe, and be able to restore it later — all without exposing what that state actually looks like on the inside.

A text editor's undo button needs the same thing. Every time a user changes the content, the editor needs a way to remember "what it looked like a moment ago," so a single undo click can put it back. The obvious first instinct is to just keep the previous value lying around somewhere. That instinct is worth following all the way through, because seeing exactly where it breaks is what makes the eventual fix feel inevitable instead of arbitrary.

Two questions are worth carrying into the rest of this topic:

  • Who should be allowed to read a saved snapshot's contents?
  • Who should decide when to take a snapshot, and when to restore one?

The answers turn out to belong to two entirely different classes — and that split is the whole pattern. It's called Memento because a memento, in plain English, is a keepsake kept specifically to remember a past moment. That's a precise description of what the snapshot class in this pattern actually is.

The simplest possible editor holds one field for its text, a method to write new content, and a method to read it back. Test it by hand: write "Hello World", then write "Hello Everyone" — the instant that second write runs, the string "Hello World" is gone. Java's garbage collector reclaims that memory, and there is no way to get it back. Ask this editor to undo, and it has nothing to undo to, because it was never built to remember anything beyond whatever was written most recently.

The natural next move is to keep every version of the content the editor has ever held, in a stack — undo naturally means "go back to the last thing that was saved," which is exactly what a stack's push and pop give you for free. So the fix looks obvious: bolt a history stack directly onto the editor. Before overwriting the content, push the old value onto the stack; on undo, pop the most recent value back into place.

Trace it by hand and it works. Write "Hello World" (stack is empty, content becomes "Hello World"). Write "Hello Everyone" (stack now holds ["Hello World"], content becomes "Hello Everyone"). Call undo() — pop "Hello World" off the stack, content becomes "Hello World" again. For this one small example, it's correct.

But stop and ask what a text editor is actually supposed to be responsible for. Its job is holding content and letting you modify it — editing. Managing a complete history of every past state, deciding how it's stored and when old entries get evicted, is a separate job: state management, not content editing. Bolting history management directly onto the editor gives the class two unrelated reasons to change, which is exactly the kind of problem the Single Responsibility Principle warns against.

Made concrete, this bolted-on stack has real costs. It won't scale past one field — add a headline field or a cursorPosition field to a real editor, and each one needs its own separate history stack, with nothing in this design saying how those separate stacks should stay coordinated with each other. The undo logic is now tangled directly into write(), so anyone reading that method has to mentally track two unrelated concerns — what writing does to the content, and what writing does to the history — at once. And the mechanism can never be reused: a completely different class, say a drawing canvas, that also wants undo support would have to copy-paste this exact stack field and this exact push/pop logic wholesale, because there's no shared, reusable "undo mechanism" here — it's baked into one specific class.

💻 Code example

class TextEditor { private String content; private Deque<String> history = new ArrayDeque<>(); // bolted directly onto the editor void write(String text) { history.push(content); // push the OLD value before it's overwritten this.content = text; } void undo() { if (!history.isEmpty()) { this.content = history.pop(); // restore the most recently saved value } } String getContent() { return content; } } // This works for one field. It gives TextEditor a second, unrelated job // (remembering history) on top of its real job (holding current content), // and that second job can never be reused by any other class.

Reason through the fix the way you'd actually think about it at a whiteboard, before writing a single new class. Something outside the editor needs to hold the history — that's the fix for the responsibility problem above. But that external "something" can't just read the editor's raw fields directly, because that breaks encapsulation just as badly, and it means every place reading those raw fields needs updating whenever the editor's internal fields change. So the editor needs a way to package "everything about my current state" into one bundle, and hand that bundle to the external history-keeper — without the history-keeper ever needing to know what's actually inside it. Later, the editor needs to be able to take that same bundle back and use it to restore itself, again without the history-keeper understanding its contents.

That reasoning splits the work into three separate responsibilities, not two: the Originator holds the real, live state and knows how to package and unpackage it; the Memento is the opaque bundle itself, a snapshot and nothing more; and the Caretaker holds a collection of these bundles and decides when to save or restore one, but never looks inside any of them.

Build the Memento first, since it's the smallest piece. It needs exactly one thing: hold a snapshot of the editor's content, and hand it back on request. The field should be final, set only through the constructor, since a snapshot that can be silently modified after the fact isn't really a snapshot at all. The getter that hands the content back is deliberately not public — it's left package-private, meaning only classes in the same package can call it. That's a deliberate choice: only the Originator that created a memento should ever be able to unpack it. The Caretaker, sitting in the same package, can hold and pass mementos around freely, but shouldn't casually read what's inside one.

The Originator — the editor — keeps its existing write() and getContent() methods completely untouched, with no history logic cluttering them anymore. It gains exactly two new methods: save(), which wraps its current state into a fresh Memento, and restore(), which takes a Memento and overwrites its own state from it. Only these two methods ever know a Memento exists.

The Caretaker holds a stack of Mementos — not raw strings — and exposes two operations: saveState(), which asks the editor to snapshot itself and pushes whatever comes back, and undo(), which pops the most recent snapshot and hands it to the editor to restore from. Look closely at what the Caretaker knows: nothing about content, nothing about strings, nothing about what a text editor even conceptually contains. It only knows how to push mementos, pop mementos, and hand them to the editor. If the editor later grows five new fields, the Caretaker's code does not change by a single character — that's the entire payoff of pulling this responsibility out.

💻 Code example

class EditorMemento { private final String content; // immutable — a snapshot that can never change once made EditorMemento(String content) { this.content = content; // the only place this field is ever assigned } String getContent() { // package-private on purpose — only the Originator should call this return content; } } class TextEditor { private String content; void write(String text) { this.content = text; } // unchanged — no history logic here anymore String getContent() { return content; } // unchanged EditorMemento save() { return new EditorMemento(this.content); // package current state into an opaque snapshot } void restore(EditorMemento memento) { this.content = memento.getContent(); // only TextEditor calls getContent() on a memento } } class Caretaker { private final Deque<EditorMemento> history = new ArrayDeque<>(); void saveState(TextEditor editor) { history.push(editor.save()); // push the MEMENTO handed back — never a raw string } void undo(TextEditor editor) { if (!history.isEmpty()) { editor.restore(history.pop()); } } }

Wire the three classes together and trace the same scenario by hand, tracking every real value, since that's what makes the pattern click rather than just look correct on paper.

After this line...editor.getContent()history stack (top → bottom)
write("Hello World")"Hello World"(empty)
saveState(editor)"Hello World"["Hello World"]
write("Hello Everyone")"Hello Everyone"["Hello World"]
saveState(editor)"Hello Everyone"["Hello Everyone", "Hello World"]
write("Hello Rahul")"Hello Rahul"unchanged — this write was never saved
1st undo(editor)"Hello Everyone"["Hello World"]
2nd undo(editor)"Hello World"(empty)

Notice "Hello Rahul" is gone forever after the first undo — saveState() was never called after writing it, so no snapshot of it ever existed. That's worth sitting with: undo only ever returns you to a state that was explicitly saved, not simply "one write ago." Save points versus every single keystroke is a genuine design decision every real undo system has to make on purpose.

Now prove the actual point of the split: extend the editor with a second field, say headline, and check what has to change in each class. EditorMemento grows one more private final field and one more constructor parameter. TextEditor.save() and restore() grow by one line each, packaging and unpackaging the new field alongside the old one. And Caretaker — the class holding the entire undo history — changes by exactly zero lines, because it never referenced content or headline in the first place. That is the concrete, measurable payoff of pulling state-management out of the editor: growth in what's being tracked never touches the class responsible for tracking it.

💻 Code example

public class Main { public static void main(String[] args) { TextEditor editor = new TextEditor(); Caretaker caretaker = new Caretaker(); editor.write("Hello World"); caretaker.saveState(editor); // snapshot #1 editor.write("Hello Everyone"); caretaker.saveState(editor); // snapshot #2 editor.write("Hello Rahul"); // never saved — will be lost on undo caretaker.undo(editor); System.out.println(editor.getContent()); // Hello Everyone caretaker.undo(editor); System.out.println(editor.getContent()); // Hello World } }

▲ Edge case — undo on an empty history

The guard if (!history.isEmpty()) means calling undo with nothing left to undo simply does nothing, silently. A real UI would typically disable the undo button entirely once history is empty rather than relying on this silent no-op, but the underlying class should never throw or corrupt state just because undo ran one time too many. For any stack-based design, always ask deliberately what the empty case should do.

▲ Edge case — this design has no redo

Once a memento is popped off history during undo, it's gone from that stack entirely — there's nowhere it's being kept for a possible redo. The fix follows directly from how the pattern already works: add a second stack, redoHistory, and whenever undo() pops a memento off history, push the state being moved away from onto redoHistory first. A subsequent redo() then pops from redoHistory and pushes back onto history.

▲ Edge case — unbounded history is a real memory cost

Every saveState() call keeps another full memento alive in memory for as long as the Caretaker itself is alive. For something auto-saving on every keystroke, this grows without bound over a long session. Real systems typically cap history size — evicting the oldest memento once a limit is hit — or save less often, at meaningful checkpoints, instead of on every single change.

▲ Edge case — mutable fields need a deep copy, not a shallow one

A String is immutable in Java, so storing it directly inside the memento was automatically safe. If the editor instead held something mutable — say a List<String> of paragraphs — naively storing a reference to that same list inside the memento means a later edit to the live list silently corrupts the "saved" snapshot too, since both point at the exact same object. Saving a mutable field correctly requires deep-copying it into the memento, not just copying the reference.

◆ Where this pattern actually shows up

  • Text and code editors — Ctrl+Z in any editor is this pattern, whether or not the implementation literally uses these three class names.
  • Drawing and graphic design applications — undoing a shape move, a color change, or a deletion works the same way: snapshot before the change, restore on undo.
  • Video game save systems — not just manual saves, but periodic checkpoint systems that snapshot progress automatically so a failure doesn't erase everything.
  • Video editing software — periodically snapshotting a project's timeline so a crash or an accidental edit doesn't destroy hours of work.
  • Database transactions — a transaction's state before commit can be discarded and rolled back to the last known-good point, which is the same "opaque, restorable checkpoint" idea applied at a completely different scale.
  • Configuration management and infrastructure tooling — capturing a known-good configuration snapshot before applying a risky change, so a failed rollout can be reverted cleanly, is this same pattern applied to entire systems rather than single objects. Java doesn't ship a literal Memento class in its standard library — this pattern is usually hand-rolled, unlike Iterator or Comparator, which have direct library equivalents. Some codebases approximate it using java.io.Serializable, serializing an object's state to bytes as a crude but workable snapshot.

Q: Why can't undo just be implemented by storing history directly inside the class being edited? : It gives that class two unrelated responsibilities — editing content, and managing state history. That's a direct Single Responsibility Principle violation, it doesn't scale cleanly as more fields get added, and the history mechanism can't be reused by any other class that also needs undo.

Q: Why is the Memento's content getter usually not public? : Only the Originator that created a memento should be able to unpack its contents. The Caretaker needs to store and pass mementos around, but reading or tampering with what's inside one is not its job — keeping the getter package-private (or otherwise restricted) enforces that boundary in code, not just by convention.

Q: What's the real difference between how the Originator relates to a Memento and how the Caretaker relates to one? : The Originator creates a memento and hands it off immediately — a lightweight dependency. The Caretaker owns and manages the full lifetime of a whole collection of mementos, pushing and popping them over the object's lifetime — a much stronger composition relationship.

Q: How would you add redo support to this design? : Add a second stack for states that have been undone. Whenever undo() pops from the main history, first push the state being moved away from onto the redo stack, so a later redo() can pop it back and restore it.

Q: What's a genuine, practical cost of this pattern worth mentioning in a trade-offs discussion? : Memory. Every saved memento holds a full snapshot of state, and for large objects or frequent saving that adds up fast. Real systems often cap history size or only save at meaningful checkpoints rather than on every single change.

Want a visual for this concept?

Generate a diagram tailored to “Memento Pattern” — the AI picks whichever visual (flowchart, comparison, sequence, etc.) best fits.

Sign in to generate a visual →

Practice quiz

Next Step

Continue to Observer Pattern← Back to all Low-Level Design & Design Patterns chapters