Without violating encapsulation, capture and externalise an object's internal state so that the object can be restored to this state later.
01
The problem Memento solves
The problem
A diagram editor. The user selects forty shapes, drags them, then hits align left and distribute vertically. Each of those touches the position, and often the size, of every selected shape. Then they press Ctrl-Z.
Reversing that by inverse operation means AlignLeftCommand remembering forty original x-coordinates, DistributeCommand remembering forty y-coordinates, and both being careful about what the drag did in between. It's doable, and it's forty chances to be wrong. Some operations have no clean inverse at all — a "tidy up connectors" command that reroutes lines has no formula that puts them back.
The obvious fix is to save the whole canvas before each operation. But now the undo stack — which is UI code — is holding the editor's internal state: the shape list, the z-order table, the connector routing cache, the selection. Every internal field the canvas has is exposed to a class that has no business knowing about it, and refactoring the canvas means changing the undo stack.
02
How the Memento pattern works
Let the object snapshot itself, and hand out a sealed box that only it can open:
The originator (the canvas) has a method that returns a memento — an object holding a copy of its internal state.
The memento's contents are opaque to everyone else. The undo stack can hold it and hand it back, but cannot read or edit a single field.
The caretaker (the undo stack) stores mementos in order. It knows nothing about shapes, z-order or routing caches — only "here is a token from earlier".
To undo, the caretaker gives a memento back to the originator, which restores itself from it. Restoring is one operation regardless of how much changed.
The encapsulation is the pattern — not the snapshotting. Anyone can save a copy of an object's fields; the reason this has a name is that the copy is stored by a class that must not be able to look inside it. Languages express that differently: Java and C# use a private nested class, C++ uses friend, and Python and JavaScript rely on convention because they can't enforce it at all. If your "memento" is a public struct with public fields that the caretaker reads and edits, you've written a snapshot — useful, but you've given up the thing the pattern was protecting.
Participants. The Originator creates and consumes mementos. The Memento holds the state and exposes a wide interface to the originator and a narrow one — usually none — to everyone else. The Caretaker keeps mementos in order and never inspects them, which is exactly why it doesn't have to change when the originator's fields do.
03
See it: undo without an inverse
Step through an editing session. Before each operation the canvas saves a memento; undo hands one back and the canvas restores itself wholesale. Notice that undoing a forty-shape align costs exactly the same as undoing a single nudge.
▶ Try it — snapshot, edit, restore
An interactive undo stack built from snapshots: each edit to the canvas is preceded by a memento capturing the whole state, and undo restores a snapshot wholesale rather than computing an inverse operation — so undoing a forty-shape alignment is one restore.
Step four is the argument for this pattern over Command-style undo. "Tidy up connectors" reroutes eighteen lines by an algorithm with no inverse — you cannot compute your way back, you can only remember where they were. And notice what the UndoStack did with all five mementos: held them in order and handed one back. It never learned what a z-order table is.
04
Memento pattern code examples
An opaque snapshot the caretaker can hold but not open — and the deep-copy trap in every language.
public final class Canvas {
private List<Shape> shapes = new ArrayList<>();
private Map<ShapeId, Integer> zOrder = new HashMap<>();
/** The memento. Private constructor and private fields: only Canvas can
* read it, which is the entire point of the pattern. */
public static final class Memento {
private final List<Shape> shapes;
private final Map<ShapeId, Integer> zOrder;
private Memento(List<Shape> shapes, Map<ShapeId, Integer> zOrder) {
this.shapes = shapes;
this.zOrder = zOrder;
}
}
public Memento save() {
// DEEP copy. `new ArrayList<>(shapes)` copies the list but shares the
// Shape objects — mutate one afterwards and your "snapshot" changes too.
return new Memento(
shapes.stream().map(Shape::copy).toList(),
Map.copyOf(zOrder));
}
public void restore(Memento m) {
this.shapes = new ArrayList<>(m.shapes);
this.zOrder = new HashMap<>(m.zOrder);
}
}
/** The caretaker holds mementos and cannot see inside a single one. */
public final class UndoStack {
private final Deque<Canvas.Memento> stack = new ArrayDeque<>();
public void record(Canvas canvas) { stack.push(canvas.save()); }
public void undo(Canvas canvas) {
if (!stack.isEmpty()) canvas.restore(stack.pop());
}
}
// Serialization is NOT a substitute: it exposes the fields to anything that can
// read the stream, which gives away the encapsulation you came for.
import copy
from dataclasses import dataclass, field
@dataclass(frozen=True)
class _Memento:
"""Leading underscore is the whole enforcement mechanism Python offers.
There is no `private`, so the opacity is a convention. Freezing the
dataclass at least stops the caretaker mutating a snapshot by accident,
which is the failure that actually happens.
"""
shapes: tuple[Shape, ...]
z_order: tuple[tuple[ShapeId, int], ...]
class Canvas:
def __init__(self) -> None:
self._shapes: list[Shape] = []
self._z_order: dict[ShapeId, int] = {}
def save(self) -> _Memento:
# deepcopy, not copy: a shallow copy of the list shares the Shape
# objects, so moving a shape later would silently rewrite history.
return _Memento(
shapes=tuple(copy.deepcopy(s) for s in self._shapes),
z_order=tuple(self._z_order.items()),
)
def restore(self, memento: _Memento) -> None:
self._shapes = [copy.deepcopy(s) for s in memento.shapes]
self._z_order = dict(memento.z_order)
class UndoStack:
def __init__(self) -> None:
self._stack: list[_Memento] = []
def record(self, canvas: Canvas) -> None: self._stack.append(canvas.save())
def undo(self, canvas: Canvas) -> None:
if self._stack:
canvas.restore(self._stack.pop())
# deepcopy is correct and slow. For big documents, prefer immutable structures
# and STRUCTURAL SHARING: if shapes never mutate, a snapshot is just a reference
# to the current tuple, and unchanged sub-trees are shared between snapshots.
class Canvas {
public:
class Memento {
// Only Canvas can construct or read one.
friend class Canvas;
Memento(std::vector<Shape> shapes, ZOrder z)
: shapes_(std::move(shapes)), z_(std::move(z)) {}
std::vector<Shape> shapes_;
ZOrder z_;
};
Memento save() const { return Memento{shapes_, z_}; } // value semantics = deep copy
void restore(const Memento& m) {
shapes_ = m.shapes_;
z_ = m.z_;
}
private:
std::vector<Shape> shapes_;
ZOrder z_;
};
// `friend` is the cleanest expression of Memento in any of these languages:
// the compiler enforces that exactly one other class may look inside.
//
// C++ value semantics also make the deep copy automatic — copying a
// vector<Shape> copies the shapes. The trap is the opposite one: if Shape
// holds a raw pointer or a shared_ptr to mutable data, the copy shares it and
// your snapshot is not a snapshot. Prefer value types inside a memento.
//
// For large documents, hold shared_ptr<const State> instead: snapshots then
// share unchanged data and cost a pointer copy each.
public sealed class Canvas
{
private ImmutableList<Shape> _shapes = ImmutableList<Shape>.Empty;
private ImmutableDictionary<ShapeId, int> _zOrder = ImmutableDictionary<ShapeId, int>.Empty;
// The memento type is nested and private-constructed: the caretaker can
// hold a CanvasMemento but cannot read or build one.
public sealed class CanvasMemento
{
internal ImmutableList<Shape> Shapes { get; }
internal ImmutableDictionary<ShapeId, int> ZOrder { get; }
internal CanvasMemento(ImmutableList<Shape> shapes, ImmutableDictionary<ShapeId, int> z)
=> (Shapes, ZOrder) = (shapes, z);
}
// Immutable collections make save() O(1): nothing is copied, because
// nothing can change underneath the snapshot.
public CanvasMemento Save() => new(_shapes, _zOrder);
public void Restore(CanvasMemento memento)
=> (_shapes, _zOrder) = (memento.Shapes, memento.ZOrder);
}
public sealed class UndoStack
{
private readonly Stack<Canvas.CanvasMemento> _stack = new();
public void Record(Canvas canvas) => _stack.Push(canvas.Save());
public void Undo(Canvas canvas) { if (_stack.TryPop(out var m)) canvas.Restore(m); }
}
// This is the version worth copying into other languages: with immutable data,
// Memento stops being about copying and becomes about keeping references.
class Canvas {
#shapes = []
#zOrder = new Map()
// #private fields are genuinely private at runtime, so the returned object
// can hold state that only Canvas methods can read back.
save() {
return Object.freeze({
// structuredClone deep-copies without JSON's losses: it preserves Map,
// Set, Date and cyclic references, none of which survive
// JSON.parse(JSON.stringify(x)) — the copy people usually reach for.
shapes: structuredClone(this.#shapes),
zOrder: structuredClone(this.#zOrder),
})
}
restore(memento) {
this.#shapes = structuredClone(memento.shapes)
this.#zOrder = structuredClone(memento.zOrder)
}
}
class UndoStack {
#stack = []
record(canvas) { this.#stack.push(canvas.save()) }
undo(canvas) {
const memento = this.#stack.pop()
if (memento) canvas.restore(memento)
}
}
// Redux time-travel debugging is this pattern at application scale: the store
// keeps previous states, and because reducers return new objects instead of
// mutating, each "snapshot" shares all the unchanged parts of the tree.
package editor
// Go has no private-to-another-type, so opacity comes from the PACKAGE:
// unexported fields are invisible outside this file's package, which is
// exactly the boundary the pattern needs.
type memento struct {
shapes []Shape
zOrder map[ShapeID]int
}
type Canvas struct {
shapes []Shape
zOrder map[ShapeID]int
}
func (c *Canvas) Save() *memento {
// The trap: `append([]Shape(nil), c.shapes...)` copies the SLICE but the
// elements are copied by value only if Shape has no reference fields. A
// Shape holding a []Point or a map shares that data with the snapshot.
shapes := make([]Shape, len(c.shapes))
for i, s := range c.shapes {
shapes[i] = s.Clone() // deep clone, per shape
}
z := make(map[ShapeID]int, len(c.zOrder))
maps.Copy(z, c.zOrder) // maps are references — always copy
return &memento{shapes: shapes, zOrder: z}
}
func (c *Canvas) Restore(m *memento) {
c.shapes = slices.Clone(m.shapes)
c.zOrder = maps.Clone(m.zOrder)
}
// UndoStack holds []*memento and can't touch a field of one from another
// package — the compiler enforces the caretaker's ignorance.
type ShapeId = string & { readonly __brand: 'ShapeId' }
// The memento type is exported as an OPAQUE token: callers can hold it and
// pass it back, and the type system refuses to let them read it.
declare const memento: unique symbol
export type CanvasMemento = { readonly [memento]: true }
interface InternalMemento extends CanvasMemento {
readonly shapes: readonly Shape[]
readonly zOrder: ReadonlyMap<ShapeId, number>
}
export class Canvas {
#shapes: readonly Shape[] = []
#zOrder: ReadonlyMap<ShapeId, number> = new Map()
save(): CanvasMemento {
// Everything is readonly, so the snapshot is just a pair of references —
// no copying, and nothing can change underneath it.
return { shapes: this.#shapes, zOrder: this.#zOrder } as InternalMemento
}
restore(m: CanvasMemento): void {
const internal = m as InternalMemento
this.#shapes = internal.shapes
this.#zOrder = internal.zOrder
}
}
export class UndoStack {
#stack: CanvasMemento[] = []
record(canvas: Canvas): void { this.#stack.push(canvas.save()) }
undo(canvas: Canvas): void {
const m = this.#stack.pop()
if (m) canvas.restore(m)
}
// `m.shapes` here is a compile error — the caretaker holds a token, not data.
}
Read across the tabs: two themes run through all seven. The first is how opacity is enforced — C++ says friend, Java and C# use a nested type with private construction, Go uses package boundaries, TypeScript can fake it with a branded opaque type, and Python relies on an underscore and good manners. The second is the deep-copy trap, and it catches people in every language: a shallow copy of a list shares the objects inside it, so the "snapshot" changes when the original does. The best answer, visible in the C# and TypeScript tabs, is to make the state immutable — then a memento costs a reference copy and the whole problem disappears.
05
How to implement Memento
Give the originator save() and restore(). Nobody else should assemble or interpret a snapshot.
Make the memento opaque with whatever your language offers: a private nested class, friend, package scoping, or a branded token type.
Copy deeply, or make the state immutable so you don't have to. A shallow copy that shares mutable objects is the classic silent failure.
Prefer immutable state and structural sharing for anything large — snapshots then cost a pointer instead of a document.
Bound the history. An unlimited undo stack over big snapshots is a memory leak with a friendly name; cap it, or store deltas and snapshot every N steps.
Keep external resources out: open files, sockets and database handles cannot be restored by assignment. Snapshot the data, re-acquire the resource.
Restore atomically — a half-restored object is worse than no undo at all.
06
When to use Memento — and when not to
Use it when you need a snapshot of an object's state to restore later, and exposing that state directly would break encapsulation. It's the right choice when operations have no clean inverse, when one operation touches a lot of state, or when you want checkpoints — undo, transactions with rollback, wizard back buttons, game saves.
Where it goes wrong
The shallow copy. The snapshot holds the same object references as the live state, so editing a shape afterwards rewrites history. It looks like it works — undo runs without error and simply restores the current values.
Memory. Fifty snapshots of a large document is fifty documents. Cap the history, store deltas with periodic full snapshots, or move to immutable structures that share what didn't change.
A memento that isn't opaque. A public struct the caretaker reads and edits is a snapshot, not a memento — you've kept the copying and thrown away the encapsulation, and now the undo stack breaks when the originator gains a field.
External resources in the state. Restoring an object that holds an open socket or file handle restores a reference to something that has since closed. Snapshot the data, not the connection.
Snapshots taken at the wrong moment. Save before the operation, not after, and take exactly one per user-visible action — otherwise Ctrl-Z undoes half a drag.
You want to…
Use
Because
Restore an object's whole prior state
Memento
Snapshot and restore, with the internals kept private.
A robust iterator may snapshot the collection — Memento in a supporting role.
07
Quick check
🧠 Quick check
Your save() returns new ArrayList<>(shapes). Users report that undo "does nothing". Why?
new ArrayList<>(shapes) copies the list — the container — not the objects in it. Both lists point at the same forty Shape instances, so when the align operation sets shape.x = 80, it changes the object the snapshot is holding. Restore then dutifully puts back a list of shapes whose coordinates are already the new ones, and the user sees nothing happen. The fix is to copy each element too (shapes.stream().map(Shape::copy)) or, better, to make Shape immutable — then a snapshot is a reference copy, it's fast, and this bug becomes impossible to write.
In the wild
C#System.Transactions rollback, and immutable collections making Save() an O(1) reference copy rather than a document copy.
JavaScriptRedux time-travel debugging — the store keeps previous states, and immutable reducer updates give structural sharing for free.
Pythoncopy.deepcopy and the pickling hooks __getstate__/__setstate__, which are the memento interface under another name.
JavaDatabase savepoints through JDBC (Connection.setSavepoint) — a token you hold and hand back, whose contents belong entirely to the database.
C++Qt's QUndoCommand subclasses storing prior state, and the friend idiom that lets exactly one class read the snapshot.
GoPersistent-structure libraries and value-copy snapshots; maps.Clone and slices.Clone exist precisely because the shallow copy is the default.
What is the difference between the Memento and Command patterns for undo?
Command undoes by applying an inverse: it remembers just enough to reverse what it did — the deleted text, the previous value. That's memory-efficient and exact when the operation is small and reversible. Memento undoes by restoring a snapshot of the whole state, which costs more memory but works when an operation touches a great deal of state or has no inverse at all (a re-layout, a reroute, a solver). Real editors use both: commands for small edits, a memento inside the command for big ones.
How is Memento different from just copying an object?
The copying is the easy part; the encapsulation is the pattern. A memento is stored by a caretaker — an undo stack, a history list — that must be able to hold it and hand it back without being able to read or modify it. That's what keeps the caretaker from depending on the originator's internals, so adding a field to the originator doesn't ripple into the undo system. If your snapshot is a public object the caretaker reads, you have a copy with extra steps.
How do you keep Memento from using too much memory?
Four levers, roughly in order of payoff. Make the state immutable so snapshots share everything unchanged — this turns a copy into a pointer and is by far the biggest win. Store deltas with a full snapshot every N steps, the approach databases and version-control systems use. Cap the history at a fixed depth. And snapshot only what can actually change — derived caches can be recomputed rather than stored.
Why does my undo appear to do nothing?
Almost always a shallow copy. Copying the collection but not the objects inside it means the snapshot and the live state share the same instances, so every later mutation edits the snapshot too — undo restores values that are already current, and nothing visibly happens. Deep-copy the elements, or make them immutable. The second most common cause is snapshotting after the operation instead of before it.
Can I use serialization to implement Memento?
You can, and it's a reasonable engine for the copy — especially when the snapshot has to outlive the process, as in a game save. Just be aware of what you're trading: a serialised form is readable by anything that can read the bytes, so the encapsulation the pattern protects is gone, and the format becomes something you have to version. Also watch for the usual serialisation losses — JSON.parse(JSON.stringify(x)) silently destroys Map, Set, Date and cycles, which is why structuredClone exists.
Finish the handbook, earn the certificate
All 23 Gang-of-Four patterns, each with a simulator you can click and code in seven languages. Free, no signup.