Beginner11 min readDesign Patternslive prototype

Memento

Capture an object's private state in a sealed snapshot so it can be restored later — without breaking encapsulation. Think of a game save file: the save-slot menu stores your saves, but only the *game itself* can open one and put your character back exactly where they were.

The idea

What it is

Memento answers a very natural question: how do I add undo? Your object has rich private state — a game character's level, HP, and position; an editor's text and cursor; a form's half-filled fields — and you want to snapshot it now and roll back to that snapshot later. The obvious hack is to read every field out into a copy, but that forces you to make everything publicly readable and writable, which shreds [[encapsulation]]. Memento gives you snapshots without opening the object up.

Think of a game save file. When you hit 'Save game', the game packs its own state into a save file — a sealed box. The save-slot menu happily stores that box, shows it as 'SAVE #2', lets you pick it later… but it cannot open it. It has no idea what level you were or where you stood, and it certainly can't edit your HP. Only the game itself, when you hit 'Load', unpacks the box and restores everything. That sealed-box property is the entire point of the pattern.

The one sentence to remember

Memento lets an object capture its own state in a sealed snapshot that outsiders can store and hand back — but never open — so the object can be restored to a past moment without ever exposing its internals.

Why 'sealed' matters so much

If the snapshot were just a public bag of fields, any code could read it, tweak it, and load a corrupted 'save' back in — the object would lose all control over its own invariants. A memento is opaque to everyone except the object that made it, so the state stays as private in the snapshot as it was in the object.

Mechanics

How it works

The three participants

The pattern splits the undo problem across three roles, and the split is the pattern — each role knows exactly as much as it needs and no more:

  • Originator — the object with the precious private state (the game). It has two extra methods: save() packs its current state into a memento, and restore(m) unpacks a memento back into itself. It is the only thing that ever reads a memento's contents.
  • Memento — the sealed snapshot (the save file). It's a dumb, opaque box: ideally immutable (built once in save(), never changed — see [[immutability-and-value-objects]]) and unreadable from outside. It exposes nothing useful to anyone but its originator.
  • Caretaker — the history keeper (the save-slot menu). It asks the originator to save(), stores the mementos in slots or a stack, and later hands one back for restore(m). It stores and organises — it never looks inside.

Why not just copy the fields from outside?

Imagine the save menu doing the job itself: snapshot = { level: game.getLevel(), hp: game.getHp(), x: game.getX(), ... } and later game.setLevel(...), game.setHp(...). To pull that off, the game must grow a public getter and setter for every single field — its internals become everyone's business, any code can now half-restore it or set nonsense values, and each time the game adds a field, every snapshot site silently goes stale. That's [[encapsulation]] destroyed for the sake of undo. Memento flips the responsibility: the object snapshots itself, so its fields can stay private and the snapshot format is its own internal secret.

typescript
class Game {
  private level = 1;            // 🔒 all private — no getters/setters needed
  private hp = 100;
  private pos = 0;

  save(): SaveFile {            // originator packs its OWN state
    return new SaveFile(this.level, this.hp, this.pos);
  }
  restore(s: SaveFile): void {  // and is the ONLY reader of a save
    this.level = s["level"]; this.hp = s["hp"]; this.pos = s["pos"];
  }
}

class SaveFile {                // memento: built once, then sealed
  constructor(
    private readonly level: number,
    private readonly hp: number,
    private readonly pos: number,
  ) {}
}

class SaveSlots {               // caretaker: stores boxes, never opens them
  private slots: SaveFile[] = [];
  store(s: SaveFile) { this.slots.push(s); }
  pick(i: number): SaveFile { return this.slots[i]; }
}

How sealed can it really be?

How strictly the memento is sealed depends on the language. Java can nest the memento as a private inner class of the originator, so outsiders literally cannot name its fields. C++ uses a friend declaration. TypeScript and Python enforce it mostly by convention (private/readonly fields, underscore names) — the design intent is identical even when the compiler polices it less.

Memento + Command: the classic undo duo

Memento pairs beautifully with [[command]]. A command object, right before it executes, asks the originator for a memento and tucks it away; undo() is then just originator.restore(saved). The command doesn't need to know how to reverse the operation — it just rewinds to the checkpoint. This is how many real editors implement undo stacks.

The cost: snapshots aren't free

Every save copies state. If the state is large (a big document) or you snapshot on every keystroke, memory climbs fast — the prototype's three fixed slots that recycle the oldest is a miniature of the real answer: cap the history. Other real-world tactics: snapshot only every N operations, store diffs (deltas) between snapshots instead of full copies, or share unchanged parts between snapshots (persistent data structures). You'll meet the same idea in the wild as editor undo history limits, database savepoints inside transactions, and games keeping only your last few autosaves.

  • Editor undo — each undo step is (conceptually) a memento of document state; real editors mix in diffs to keep memory sane.
  • Database transactions & savepointsSAVEPOINT marks a sealed checkpoint you can ROLLBACK TO without the client ever seeing internal page state.
  • Game saves — the pattern's poster child: sealed files the menu lists but only the engine can interpret.
  • Form draft restore — snapshot the form state before a risky action (navigation, autofill) and offer 'restore draft'.

Interactive prototype

See it. Build it. Break it.

A sandboxed, hands-on simulation — no setup, no install. Play with it as you read.

About this simulation

A tiny game with save slots. On the left is the game (the originator): your 🎮 hero has a level, an HP bar, and a position on a small map — all private state. Play a little (Fight, Potion, Move), then hit 💾 Save: a sealed cartridge lands in a save slot showing only SAVE #n · 🔒 — deliberately no stats visible, because the slot menu (the caretaker) stores mementos but cannot look inside them. Try the peek? button on a filled slot and watch the lock refuse to open. Then press ⏪ Load on any slot and the game snaps back to exactly that moment — level, HP, and position all restored by the game itself, the only object allowed to read the save. Fill a fourth save and watch the oldest slot recycle: history costs memory.

Hands-on

Try these yourself

Open the prototype above, predict what happens, then verify.

try 01

Play, then save

In the prototype, click ⚔ Fight a couple of times (level goes up, HP goes down), 🧪 Potion once, and 👣 Move to walk the dot across the map. Now click 💾 Save. A cartridge appears in slot 1 reading only 'SAVE #1 · 🔒' — notice it shows no stats at all. The save-slot panel is the caretaker: it holds your snapshot but has no window into it.

try 02

Try to peek — then load

Click the small 'peek?' button on your filled slot. The lock just wobbles: the caretaker can't open a memento — only the game can. Now mutate the game some more (Fight, Move), then press ⏪ Load on slot 1. Watch the level, HP bar, and position dot snap back to exactly the moment you saved. The game read its own sealed save and restored itself; nothing on the outside ever touched its fields.

try 03

Fill the history and watch it recycle

Make three more saves in different states so all 3 slots are full, then save a 4th time. The oldest slot fades and gets recycled with the new save. That's the memory cost of Memento made visible: snapshots pile up, so real systems cap the history, keep diffs, or drop old checkpoints — exactly like a game keeping only your last few autosaves. Hit ↺ Reset to start fresh.

In practice

When to use it — and what trips people up

Reach for Memento when rollback matters and privacy matters

The pattern earns its keep when both halves of its promise are needed — you want to rewind, and you don't want to expose internals to do it:

  • Undo / rollback of rich private state — editors, drawing apps, game state, wizards with multi-step forms: anything where 'take me back to how it was' must be exact and the state isn't public.
  • Checkpoints before risky operations — snapshot before applying a migration, running an irreversible command, or letting the user try a destructive action; restore if it goes wrong (the database SAVEPOINT idea).
  • Pairing with [[command]] — each command stores a memento before executing, making undo trivial and uniform across wildly different operations.

When NOT to use it

  • The state is tiny or already public — a single number, a plain settings struct: just copy the value. Wrapping int score in an originator/memento/caretaker trio is ceremony, not design.
  • History would blow memory — thousands of snapshots of a large object is a leak with a design pattern's name on it. If you can't cap, diff, or share structure between snapshots, reconsider (event sourcing or reversible operations may fit better).
  • *You need to inspect history, not just restore it* — a memento is deliberately opaque. If the UI must show 'what changed', you want an explicit change log, not sealed boxes.

What it gives you

  • Undo/rollback without breaking encapsulation — the object's fields stay private; no getter/setter explosion just to support snapshots.
  • Exact, trustworthy restores — the originator packs and unpacks its own state, so a restore can never be partial or inconsistent the way field-by-field copying from outside can.
  • Clean separation of duties — the caretaker owns when to save and which snapshot to keep, the originator owns what state means; history logic stays out of the business object.
  • Immutable snapshots are safe to share — a sealed, read-only memento can be stored, passed around, even serialized, with no risk of anyone corrupting the saved state.

Common mistakes

  • Memory cost — every snapshot is a copy; frequent saves of large state add up fast, so real systems must cap history, snapshot less often, or store diffs.
  • True sealing is language-dependent — Java nested classes and C++ friends enforce it; TypeScript and Python mostly rely on convention, so discipline is part of the design.
  • Caretaker lifetime bugs — the caretaker decides how long saves live; forgetting to prune old mementos is a classic slow leak.
  • Hidden staleness — a restored memento silently rewinds everything the originator owns; if other objects depended on the newer state, restoring can leave the wider system inconsistent.

Reference

Code & further reading

A minimal reference implementation and pointers worth bookmarking.

// MEMENTO — a sealed save file. Built once, readonly after.
class SaveFile {
  constructor(
    readonly level: number,
    readonly hp: number,
    readonly pos: number,
  ) {}
}

// ORIGINATOR — the game. Its state stays private; it alone
// knows how to pack a save and how to unpack one.
class Game {
  private level = 1;
  private hp = 100;
  private pos = 0;

  fight()  { this.level += 1; this.hp -= 20; }
  potion() { this.hp = Math.min(100, this.hp + 30); }
  move()   { this.pos += 1; }

  save(): SaveFile {                 // pack my OWN state
    return new SaveFile(this.level, this.hp, this.pos);
  }
  restore(s: SaveFile): void {       // only I read a save back
    this.level = s.level; this.hp = s.hp; this.pos = s.pos;
  }
  toString() { return `Lv${this.level} HP${this.hp} @${this.pos}`; }
}

// CARETAKER — the save-slot menu. Stores saves, never opens them.
class SaveSlots {
  private slots: SaveFile[] = [];
  store(s: SaveFile) { this.slots.push(s); }
  pick(i: number): SaveFile { return this.slots[i]; }
}

const game = new Game();
const menu = new SaveSlots();

game.fight(); game.move();
menu.store(game.save());            // 💾 sealed snapshot into slot 0

game.fight(); game.fight();         // keep playing...
console.log(`${game}`);             // Lv4 HP40 @1

game.restore(menu.pick(0));         // ⏪ load slot 0
console.log(`${game}`);             // Lv2 HP80 @1 — exact restore

References & further reading

6 sources

Knowledge check

Did it land?

Quick questions, answers revealed on submit. Sign in to save your best score.

question 01 / 05

What is the intent of the Memento pattern, in one sentence?

question 02 / 05

In the game-save analogy, who plays which role?

question 03 / 05

Why not skip the pattern and have the save menu copy the game's fields itself via public getters and setters?

question 04 / 05

What is the caretaker allowed to do with a memento?

question 05 / 05

Your editor snapshots the full document on every keystroke and memory usage is exploding. What's the idiomatic fix?

0/5 answered