Skip to main content
CleanCodeMastery

Memento Pattern: Save Your Game Before the Boss Fight

Understand the Memento pattern through a video game save point story, with TypeScript and Python undo examples, diagrams, tables and easy practice tasks.

25 min read Updated June 11, 2026beginner
design-patternsbehavioral-patternsmementoundosnapshottypescriptpythonencapsulation

The Night Before the Boss Fight 🐉

It is Sunday evening. Rohan, fourteen years old, has been playing his favourite adventure game since lunch. His little sister Anaya is watching from the edge of the bed, giving unwanted advice as little sisters do.

Rohan's character has had a glorious day. He has collected 4,500 gold coins. He has found the Rare Sword of the North in a hidden cave. His health bar is full and green. And now, on the screen, stands a huge stone door with fire glowing behind the cracks. Behind that door waits the final boss — the giant fire dragon.

Anaya says, "Just go in! Attack!"

But Rohan is smart. He has lost to bosses before. What does he do first? He walks his character to the glowing save point and presses Save Game.

Why? Because he knows exactly what may happen. The dragon may finish him in thirty seconds. His coins, his sword, his whole Sunday — all could be wiped out. But with a save file, nothing is ever truly lost. If he fails, he presses Load Game, and boom — he is standing at the door again, coins and sword intact, ready for round two.

Rohan opens the door. The dragon roars. Two minutes later the screen says YOU DIED in big red letters. Anaya laughs. Rohan calmly presses Load Game — and there he is, back at the door, 4,500 coins, sword shining. Round two begins.

Now notice something quietly brilliant about that save file. Rohan owns it. He can keep it, keep five of them, delete old ones. But can he open it and edit it? Can he change coins = 4500 to coins = 99999 in Notepad? No. The save file is a sealed packet of bytes. Only the game engine knows what is inside and how to read it back.

This is the Memento pattern, exactly and completely:

  • The game engine creates snapshots of its own state and can restore from them. We call it the Originator.
  • The save file is the snapshot — sealed, unreadable to outsiders. We call it the Memento.
  • Rohan holds the save files without ever looking inside. We call him the Caretaker.

You have used this pattern hundreds of times without knowing its name. Every Ctrl+Z in your homework document, every game checkpoint, every "restore previous version" — all Memento. In this post we will build Rohan's save system three times, draw it from every angle, and learn why the sealing is the real magic, not the saving.

What Is the Memento Pattern?

Here is the definition in simple words.

Memento is a behavioral design pattern that lets you capture an object's internal state into a snapshot object, store that snapshot somewhere safe, and later restore the object to that exact state — without ever exposing the object's private details to the outside world.

The last part is the heart of the pattern. Anyone can copy public data. The clever trick of Memento is saving and restoring private state while keeping it private. The object takes its own photo, seals it in an envelope, and hands the envelope out. The keeper of envelopes can store them, stack them, return them — but never open them.

The pattern is also called Snapshot or Token in some books. Three roles work together:

RoleIn Rohan's storyJobCan read the snapshot?
OriginatorThe game engineCreates mementos with save(), rewinds with restore(m)✅ Yes — full access
MementoThe save fileHolds one frozen copy of state, immutable foreverIt is the snapshot
CaretakerRohan the playerDecides when to save and when to restore; stores the stack❌ Never

A useful pair of words for college viva: the originator gets a wide interface to the memento (it can read everything inside), while the caretaker gets a narrow interface (it can only hold and pass the memento). Same object, two views, enforced by access control.

💡

Memory trick: Originator = game engine, Memento = save file, Caretaker = Rohan. Rohan keeps save files but cannot read them. The engine reads them but does not manage the shelf. Each role does exactly one job — that division is the pattern.

The Problem It Solves: The Open Locker

Why do we need a special pattern? Can we not just copy the fields ourselves? Let us try the naive way and watch it break in Rohan's own game.

Suppose the "obvious" approach: let some History class reach into the game and copy fields one by one:

// ❌ BAD: the history list reads the game's internals directly
class Game {
  // To let History copy these, we are FORCED to make them public!
  public level = 1;
  public coins = 0;
  public health = 100;
  public inventory: string[] = [];
}
 
class History {
  snapshots: { level: number; coins: number; health: number; inventory: string[] }[] = [];
 
  backup(game: Game) {
    this.snapshots.push({
      level: game.level,
      coins: game.coins,
      health: game.health,
      inventory: game.inventory, // ⚠️ a nasty bug is hiding here too!
    });
  }
}

This "works", but four serious problems are hiding inside:

  1. Encapsulation is broken. To copy the fields, History needs public access to every field. The moment those fields are public, any code anywhere can write game.coins = -5 and corrupt the game. Anaya could write a cheat script in one line. The game can no longer protect its own rules.
  2. It is fragile. Next month we add a new field — say questsCompleted. We must remember to update History.backup() too. If we forget, "load game" silently restores a half-correct state. Such bugs hide for months and ruin save files that players spent weeks building.
  3. The history class knows too much. A history keeper should be a dumb shelf. Why should a shelf understand what "health" and "inventory" mean? Because of this coupling, our History can never be reused for a text editor or a drawing app.
  4. The haunted save file. Look at inventory: game.inventory. That copies the reference, not the array. When Rohan later picks up a new item, the "saved" snapshot changes too! The save file is quietly being edited after saving. When he restores, he does not go back in time — he stays in the corrupted present. Horror movie material.

Here is the difference in shape, side by side:

Figure 1: The naive way forces the game to open its private fields to everyone. The Memento way keeps them sealed.

The Memento pattern flips the responsibility. Instead of an outsider reaching in, the game itself produces a sealed snapshot and hands it out. The game can read its own private fields — no special access needed, because they are its own. The history keeper stores sealed envelopes. Everyone does only their own job.

College corner: this is encapsulation of snapshots, and it is the exam-worthy core of the pattern. Encapsulation normally means "private state plus public behaviour". Memento extends it across time: the externalized state (the snapshot) must remain as private as the live state. The Gang of Four achieve this with language tricks — in C++ via friend, in Java and C# via nested private classes where the memento class sits inside the originator, so only the originator can touch its fields. The caretaker compiles against a narrow marker interface (sometimes literally an empty interface IMemento). If your design lets the caretaker call memento.getCoins(), you have not implemented Memento; you have implemented a public struct with extra steps.

How It Works, Step by Step

Here is the recipe you can follow in any language:

  1. Find the originator. Which object's state must survive a rewind? The game engine, the editor document, the drawing canvas.
  2. Create the memento class. Give it fields for exactly the state you want to capture. Set them in the constructor and never allow changes afterwards — a snapshot must not drift.
  3. Seal the memento. Use your language's tools: a nested class in Java/C#, private fields readable only by the originator, or even a serialized string. The caretaker should be able to hold a memento but not read it.
  4. Add save() to the originator. It copies the current state into a new memento. Copy deeply if the state contains arrays or objects.
  5. Add restore(memento) to the originator. It overwrites the current state from the memento.
  6. Build the caretaker. Usually a simple stack: backup() pushes originator.save(); undo() pops a memento and calls originator.restore() with it.

Watch one full save-and-undo round trip from Rohan's Sunday:

Figure 2: Save before the boss fight, lose the fight, restore. Rohan never opens the save file.

The flow is always the same triangle. Caretaker asks; originator snapshots; caretaker stores. Later: caretaker returns the envelope; originator rewinds itself. The memento just travels, silent and sealed.

And here is the whole evening as Rohan lived it, feelings included:

Figure 3: Rohan's Sunday evening as a journey. The save point turns disaster into a small bump.

Look at that score of 1 — losing everything would normally be the end of the evening. Because one snapshot existed, the lowest point of the night lasted exactly as long as a loading screen.

The Blueprint 📐

Here is the class structure, the one to draw in exams. Note carefully which arrows exist and which do not:

Figure 4: Memento structure — the caretaker holds mementos but has no arrow into their data.

The arrow that is missing tells the story: there is no line from SaveSlotManager into the fields of SaveFile. The manager holds the boxes; only Game opens them.

The originator also moves through a small, predictable set of states. This is the rhythm of every undo system ever built:

Figure 5: The life of the game state — play, snapshot, disaster, rewind.

Notice that Defeated has an exit. Without the Memento pattern, Defeated would be a dead end and Rohan's Sunday would be gone forever.

Real Code: Rohan's Game in TypeScript

Let us build the adventure game properly. Save before the boss, fight, lose, restore. Read the comments — they carry the story.

// --- The Memento: a sealed save file ---
// All fields are private and readonly. Nothing can change after creation.
class SaveFile {
  constructor(
    private readonly level: number,
    private readonly coins: number,
    private readonly health: number,
    private readonly inventory: string[],
    public readonly savedAt: Date // tiny bit of metadata is fine to expose
  ) {}
 
  // These getters exist ONLY for the Game to use during restore.
  // In Java/C# we would seal this with a nested class. In TypeScript,
  // we keep the rule by discipline: only Game calls these.
  getState() {
    return {
      level: this.level,
      coins: this.coins,
      health: this.health,
      inventory: [...this.inventory], // give out a copy, never the original
    };
  }
}
 
// --- The Originator: the game engine that owns the real state ---
class Game {
  private level = 1;
  private coins = 0;
  private health = 100;
  private inventory: string[] = [];
 
  // Normal game play methods
  collectCoins(amount: number) {
    this.coins += amount;
    console.log(`Collected ${amount} coins. Total: ${this.coins}`);
  }
 
  pickUpItem(item: string) {
    this.inventory.push(item);
    console.log(`Picked up: ${item}`);
  }
 
  fightDragon() {
    // Spoiler: the dragon wins this time.
    this.health = 0;
    this.coins = 0;
    this.inventory = [];
    console.log("The dragon defeated you! Health 0, everything lost...");
  }
 
  // POWER 1: take a snapshot of MY OWN private state.
  // No outsider needed. Deep copy the array so the snapshot never drifts.
  save(): SaveFile {
    console.log("💾 Game saved at the save point.");
    return new SaveFile(this.level, this.coins, this.health, [...this.inventory], new Date());
  }
 
  // POWER 2: rewind myself from a snapshot.
  restore(file: SaveFile) {
    const s = file.getState();
    this.level = s.level;
    this.coins = s.coins;
    this.health = s.health;
    this.inventory = s.inventory;
    console.log(`⏪ Game loaded! Level ${this.level}, ${this.coins} coins, health ${this.health}.`);
  }
 
  status() {
    console.log(
      `Status -> level: ${this.level}, coins: ${this.coins}, ` +
      `health: ${this.health}, bag: [${this.inventory.join(", ")}]`
    );
  }
}
 
// --- The Caretaker: Rohan, who keeps save files but never opens them ---
class SaveSlotManager {
  private slots: SaveFile[] = [];
 
  constructor(private game: Game) {}
 
  backup() {
    this.slots.push(this.game.save()); // just stores the sealed file
  }
 
  undo() {
    const file = this.slots.pop();
    if (!file) {
      console.log("No save file found!");
      return;
    }
    this.game.restore(file); // hands it back, asks the game to rewind
  }
}
 
// --- Rohan's Sunday evening ---
const game = new Game();
const rohan = new SaveSlotManager(game);
 
game.collectCoins(4500);
game.pickUpItem("Rare Sword");
game.status();
 
rohan.backup();          // 💾 save before the boss door
 
game.fightDragon();      // disaster!
game.status();
 
rohan.undo();            // ⏪ load the save — back at the door
game.status();

The output tells the whole drama:

Collected 4500 coins. Total: 4500
Picked up: Rare Sword
Status -> level: 1, coins: 4500, health: 100, bag: [Rare Sword]
💾 Game saved at the save point.
The dragon defeated you! Health 0, everything lost...
Status -> level: 1, coins: 0, health: 0, bag: []
⏪ Game loaded! Level 1, 4500 coins, health 100.
Status -> level: 1, coins: 4500, health: 100, bag: [Rare Sword]

Three things to admire:

  1. Every field of Game stayed private. We never opened the game's internals to anyone — not to the manager, not to Anaya's cheat scripts.
  2. SaveSlotManager is completely dumb. It knows nothing about coins or health. Tomorrow it could manage save files for a totally different game, or for a text editor, without changing a line.
  3. The arrays were deep copied — once in save() and once in getState(). The snapshot can never be edited by accident after it is taken. No haunted save files here.

The Same Idea in Python: Ctrl+Z for Homework

Now the second scenario you know very well: pressing Ctrl+Z while typing your homework. Here is a tiny text editor with undo, in Python:

# --- Memento: one sealed snapshot of the document ---
class Snapshot:
    def __init__(self, text: str, cursor: int):
        # Single underscore = "private by convention" in Python
        self._text = text
        self._cursor = cursor
 
 
# --- Originator: the editor that owns the text ---
class Editor:
    def __init__(self):
        self._text = ""
        self._cursor = 0
 
    def type(self, words: str):
        self._text += words
        self._cursor = len(self._text)
        print(f'Typed. Document is now: "{self._text}"')
 
    def save(self) -> Snapshot:
        return Snapshot(self._text, self._cursor)
 
    def restore(self, snap: Snapshot):
        # Only the editor reads the snapshot's insides.
        self._text = snap._text
        self._cursor = snap._cursor
        print(f'Ctrl+Z! Document is back to: "{self._text}"')
 
 
# --- Caretaker: the undo stack ---
class UndoStack:
    def __init__(self, editor: Editor):
        self._editor = editor
        self._stack = []
 
    def backup(self):
        self._stack.append(self._editor.save())
 
    def undo(self):
        if self._stack:
            self._editor.restore(self._stack.pop())
 
 
# --- Homework time ---
editor = Editor()
undo = UndoStack(editor)
 
editor.type("The Taj Mahal is in Agra.")
undo.backup()                             # checkpoint before the next sentence
 
editor.type(" It was built in Mumbai.")   # oops, wrong fact!
undo.undo()                               # Ctrl+Z saves the day

Output:

Typed. Document is now: "The Taj Mahal is in Agra."
Typed. Document is now: "The Taj Mahal is in Agra. It was built in Mumbai."
Ctrl+Z! Document is back to: "The Taj Mahal is in Agra."

One honest note about Python: it cannot force the caretaker to stay out of the snapshot — the underscore is a polite request, not a locked door. In Java and C# we get real enforcement. Let us see that real lock.

The Sealed Version in C#: A Locked Save File 🔒

This snippet shows the trick college exams love: a private nested class. The memento lives inside the originator, so only the originator can read its fields. The outside world sees only an empty marker interface:

// The narrow interface — this is ALL the caretaker ever sees.
public interface ISaveFile { DateTime SavedAt { get; } }
 
public class Game
{
    private int _coins;
    private int _health = 100;
 
    public void CollectCoins(int amount) => _coins += amount;
    public void FightDragon() { _coins = 0; _health = 0; }
 
    // The wide interface exists only in here.
    // Nobody outside Game can even NAME this class, let alone read it.
    private sealed class SaveFile : ISaveFile
    {
        public int Coins { get; }
        public int Health { get; }
        public DateTime SavedAt { get; } = DateTime.Now;
        public SaveFile(int coins, int health) { Coins = coins; Health = health; }
    }
 
    public ISaveFile Save() => new SaveFile(_coins, _health);
 
    public void Restore(ISaveFile file)
    {
        // Only Game can unwrap the sealed envelope.
        if (file is SaveFile s) { _coins = s.Coins; _health = s.Health; }
    }
}
 
// The caretaker compiles against ISaveFile only.
// rohanSlots.Peek().Coins  -->  COMPILE ERROR. The seal is real.
var game = new Game();
var rohanSlots = new Stack<ISaveFile>();
 
game.CollectCoins(4500);
rohanSlots.Push(game.Save());   // save before the boss
game.FightDragon();             // disaster
game.Restore(rohanSlots.Pop()); // rewind — coins are back

This is the same triangle as TypeScript and Python, but now the compiler itself guards the envelope. If a teammate tries file.Coins from outside, the code does not even build. Encapsulation across time, enforced by the type system.

What Lives Inside a Save File? 📊

Students sometimes imagine a save file as "a few numbers". In real games it is much heavier, and that weight drives every design decision around Memento:

Figure 6: What typically fills a real game save — world state dwarfs the player numbers.

Because snapshots are heavy, saving often gets expensive. Watch what happens to memory if Rohan's game saved a full 5 MB snapshot at every checkpoint, versus a smarter engine that stores only the differences (diffs) between checkpoints:

Figure 7: Full snapshots eat memory linearly; diff-based snapshots stay nearly flat.

College corner: the two lines above are the classic space trade-off of this pattern. Full snapshots cost O(S × k) memory for k snapshots of state size S, but restore in O(S) with zero computation. Diff-based (incremental) snapshots cost far less space but make restore O(S + d × k) because you must replay diffs from the last full snapshot — exactly how video keyframes and Git packfiles work. A third option is abandoning snapshots entirely and storing reverse operations (the Command pattern), which costs almost nothing in space but requires every operation to be reliably invertible. Real systems mix all three: a full memento every N steps, diffs in between, commands for cheap actions.

Should You Use It Here?

Figure 8: Memento fits when state is private and rewinding matters. For public, throwaway state, skip it.

And the scan-in-ten-seconds table:

SituationUse Memento?Why
You need undo/redo of an object's state✅ YesThe classic, perfect fit
You need rollback if an operation fails (transactions)✅ YesSave before, restore on failure
You need checkpoints in a long-running process✅ YesResume from the last good snapshot
Reading state from outside would force you to make fields public✅ YesThe originator snapshots itself; privacy survives
The state is tiny and already public❌ NoA plain copy or Prototype clone is simpler
The state is huge and you would snapshot every second❌ CarefulMemory balloons; store diffs or use Command instead
You need to undo actions with outside effects (sent emails!)❌ NoNo snapshot can unsend an email; rethink the design

Where You See It in Real Software

Memento is quietly working all around you:

  • Undo/redo in editors. Text editors, drawing apps like Photoshop, and IDEs keep stacks of state snapshots (or diffs) so Ctrl+Z can rewind. This is the textbook use described by Refactoring Guru and the GoF book itself.
  • Game save systems and checkpoints. Exactly Rohan's story — consoles and PC games serialize the world state into an opaque save file that only the engine can interpret. Auto-save before a boss arena is the caretaker being kind to you.
  • Database transactions and savepoints. Before changing records, databases record enough state to roll back. A SQL SAVEPOINT works like a memento for a part of a transaction: if something fails, ROLLBACK TO SAVEPOINT restores the earlier state.
  • Version control. A Git commit behaves like a memento of your whole repository: a sealed snapshot you can return to with one command. (Git's internals are fancier — diffs and packfiles, as in Figure 7 — but the mental model fits.)
  • Virtual machine and disk snapshots. Tools like VirtualBox or cloud providers let you snapshot a whole machine and restore it after a risky experiment. System Restore on Windows is a memento of your operating system.
  • Open-source examples. The java-design-patterns repository has a clean runnable memento example, and GeeksforGeeks walks through the structure with diagrams.

Common Mistakes Students Make ⚠️

⚠️

Mistake 1: Shallow copy — the haunted save file. If the originator's state contains arrays, lists or nested objects and you copy only the references, your snapshot stays connected to the live object. Rohan picks up a new item, and the old save file changes too. When he restores, he does not go back in time — he stays in the corrupted present. Always deep-copy mutable state into the memento ([...inventory], list(items), clone methods). This single bug causes most broken undo features in student projects and in production code alike.

⚠️

Mistake 2: Keeping every snapshot forever. Each memento is a full copy of state. Save on every keystroke in a big document, and you eat memory like a buffet. Real caretakers have a policy: keep the last N snapshots, merge old ones, or store diffs (Figure 7 again). A caretaker without a cleanup policy is a memory leak in slow motion.

Three more quick ones:

  • Letting the caretaker peek inside. If your history class starts calling memento.getCoins() to display something, the seal is broken and you are back to the naive design. If the UI needs a label like "Save 3 — Sunday 7:42 PM", give the memento safe metadata only — a name and a timestamp, like our savedAt field.
  • Making mementos mutable. A snapshot with setters is not a snapshot; it is a time bomb. Make every field readonly in TypeScript, final in Java, get-only in C#.
  • Snapshotting at the wrong moment. Save before the change you may want to undo, not after. Saving after the mistake just preserves the mistake. Rohan saves before the door, not after the dragon's first fireball.

Compare With the Cousins

Memento has two cousins students mix up: Command (for undo) and Prototype (for copying).

QuestionMementoCommandPrototype
Core ideaSave a sealed snapshot of stateWrap an action as an objectClone a whole object
Undo styleRestore the old state directlyRun a reverse actionReplace object with an earlier clone
Memory costFull snapshot each timeTiny (just the action info)Full clone each time
Knows internals?Only the originator reads the mementoCommand knows what it changedClone is a normal open object
Best forUndo, rollback, checkpointsAction history, queues, redo logsQuick copies of self-contained state
Game analogySave fileReplay of your moves, reversedA twin of your character

In big editors, Command and Memento often team up: each command (like "paste paragraph") grabs a memento of the area it will change. Undoing the command restores from that memento. Command knows what happened; Memento remembers how things were. Together they make bulletproof undo.

The Whole Pattern in One Picture 🗺️

Figure 9: Everything about Memento on one mind map — revise from this before an exam.

Quick Revision Box

+--------------------------------------------------------------+
|                  MEMENTO — QUICK REVISION                    |
+--------------------------------------------------------------+
| What     : Save an object's state in a SEALED snapshot;      |
|            restore it later. Privacy stays intact.           |
| Picture  : Save Game before the boss fight.                  |
| Roles    : Originator = game engine (save/restore)           |
|            Memento    = save file (sealed, immutable)        |
|            Caretaker  = player (stores, never opens)         |
| Key calls: m = originator.save()                             |
|            originator.restore(m)                             |
| Wins     : Real undo, rollback, checkpoints; encapsulation   |
|            never breaks; caretaker stays dumb and reusable.  |
| Dangers  : Shallow copies (haunted saves!), memory bloat,    |
|            mutable mementos.                                 |
| Cousins  : Command = undo by reverse ACTION;                 |
|            Prototype = clone when state is simple.           |
| Used in  : Editor Ctrl+Z, game saves, DB savepoints,         |
|            VM snapshots, Git-style commits.                  |
+--------------------------------------------------------------+

Practice Exercises ✏️

Try these with your own hands. Start from the TypeScript game code above.

  1. Redo support. Right now SaveSlotManager only has undo. Add a second stack called redoStack. When Rohan undoes, push the current state onto the redo stack before restoring. Then implement redo(). Test the sequence: collect coins → backup → lose coins → undo → redo. Does the state move correctly both ways?

  2. Photocopy of a form. Model this real-life scene: before submitting an application form at a government office, Rohan's father keeps a photocopy. Build an ApplicationForm originator (fields: name, address, phone — all private), a Photocopy memento, and a FileCabinet caretaker that stores photocopies with dates. Submit the form, "accidentally" overwrite the address with a wrong one, and restore from the photocopy.

  3. History limit. Give the caretaker a maximum of 5 save slots. When a 6th backup arrives, the oldest one must be thrown away (like a queue at the bottom, stack at the top). Then write one sentence: why is this limit important in a real text editor where the user types thousands of characters per day?

  4. Break the seal, then feel the pain (college). Deliberately change the TypeScript SaveFile to use public fields, and let SaveSlotManager display slots[0].coins in a fancy UI label. Now add a new field mana to the game. Count every file you must edit, and find the bug that appears if you forget one. Write two sentences on what the sealed version would have prevented.

  5. Diff snapshots (challenge). Implement the second line of Figure 7: instead of saving the full inventory each time, make save() store only the items added or removed since the last save. Make restore() replay diffs backwards. Measure (or reason about) the memory difference for 100 saves of a 1,000-item inventory.

Frequently asked questions

What is the Memento pattern in one line?
It is a behavioral design pattern that lets an object save a snapshot of its own state in a sealed packet, so it can be restored to that exact state later — like a game save file.
What are the three roles in the Memento pattern?
Originator (the game engine — it creates and reads snapshots), Memento (the save file — a sealed copy of state), and Caretaker (the player — keeps save files but never opens them).
Why can't the caretaker read the memento?
That is the whole point of the pattern. If the caretaker could read or edit the snapshot, the originator's private state would leak out and encapsulation would break. The memento stays a black box to everyone except its creator.
Is Ctrl+Z (undo) really built with Memento?
Undo in editors is the classic use of this pattern. Before each change, the editor saves a memento. Pressing Ctrl+Z pops the latest memento off a stack and restores it.
Doesn't saving full snapshots waste memory?
It can, if the state is huge or you save very often. The common fixes are limiting history depth, saving diffs instead of full copies, or using the Command pattern for reverse operations.

Further reading

Related Lessons