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.
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:
| Role | In Rohan's story | Job | Can read the snapshot? |
|---|---|---|---|
| Originator | The game engine | Creates mementos with save(), rewinds with restore(m) | ✅ Yes — full access |
| Memento | The save file | Holds one frozen copy of state, immutable forever | It is the snapshot |
| Caretaker | Rohan the player | Decides 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:
- Encapsulation is broken. To copy the fields,
Historyneeds public access to every field. The moment those fields are public, any code anywhere can writegame.coins = -5and corrupt the game. Anaya could write a cheat script in one line. The game can no longer protect its own rules. - It is fragile. Next month we add a new field — say
questsCompleted. We must remember to updateHistory.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. - 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
Historycan never be reused for a text editor or a drawing app. - 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:
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:
- Find the originator. Which object's state must survive a rewind? The game engine, the editor document, the drawing canvas.
- 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.
- Seal the memento. Use your language's tools: a nested class in Java/C#,
privatefields readable only by the originator, or even a serialized string. The caretaker should be able to hold a memento but not read it. - Add
save()to the originator. It copies the current state into a new memento. Copy deeply if the state contains arrays or objects. - Add
restore(memento)to the originator. It overwrites the current state from the memento. - Build the caretaker. Usually a simple stack:
backup()pushesoriginator.save();undo()pops a memento and callsoriginator.restore()with it.
Watch one full save-and-undo round trip from Rohan's Sunday:
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:
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:
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:
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:
- Every field of
Gamestayed private. We never opened the game's internals to anyone — not to the manager, not to Anaya's cheat scripts. SaveSlotManageris 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.- The arrays were deep copied — once in
save()and once ingetState(). 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 dayOutput:
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 backThis 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:
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:
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?
And the scan-in-ten-seconds table:
| Situation | Use Memento? | Why |
|---|---|---|
| You need undo/redo of an object's state | ✅ Yes | The classic, perfect fit |
| You need rollback if an operation fails (transactions) | ✅ Yes | Save before, restore on failure |
| You need checkpoints in a long-running process | ✅ Yes | Resume from the last good snapshot |
| Reading state from outside would force you to make fields public | ✅ Yes | The originator snapshots itself; privacy survives |
| The state is tiny and already public | ❌ No | A plain copy or Prototype clone is simpler |
| The state is huge and you would snapshot every second | ❌ Careful | Memory balloons; store diffs or use Command instead |
| You need to undo actions with outside effects (sent emails!) | ❌ No | No 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
SAVEPOINTworks like a memento for a part of a transaction: if something fails,ROLLBACK TO SAVEPOINTrestores 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 oursavedAtfield. - Making mementos mutable. A snapshot with setters is not a snapshot; it is a time bomb. Make every field
readonlyin TypeScript,finalin 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).
| Question | Memento | Command | Prototype |
|---|---|---|---|
| Core idea | Save a sealed snapshot of state | Wrap an action as an object | Clone a whole object |
| Undo style | Restore the old state directly | Run a reverse action | Replace object with an earlier clone |
| Memory cost | Full snapshot each time | Tiny (just the action info) | Full clone each time |
| Knows internals? | Only the originator reads the memento | Command knows what it changed | Clone is a normal open object |
| Best for | Undo, rollback, checkpoints | Action history, queues, redo logs | Quick copies of self-contained state |
| Game analogy | Save file | Replay of your moves, reversed | A 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 🗺️
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.
-
Redo support. Right now
SaveSlotManageronly has undo. Add a second stack calledredoStack. When Rohan undoes, push the current state onto the redo stack before restoring. Then implementredo(). Test the sequence: collect coins → backup → lose coins → undo → redo. Does the state move correctly both ways? -
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
ApplicationFormoriginator (fields: name, address, phone — all private), aPhotocopymemento, and aFileCabinetcaretaker that stores photocopies with dates. Submit the form, "accidentally" overwrite the address with a wrong one, and restore from the photocopy. -
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?
-
Break the seal, then feel the pain (college). Deliberately change the TypeScript
SaveFileto usepublicfields, and letSaveSlotManagerdisplayslots[0].coinsin a fancy UI label. Now add a new fieldmanato 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. -
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. Makerestore()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
Command Pattern: Turn Every Action into an Order Slip
Learn the Command pattern through a restaurant order slip story. Simple TypeScript and C# code with undo and redo, diagrams, tables, and practice tasks.
Prototype Pattern: Make Copies Like a Xerox Machine, Not From Scratch
Learn the Prototype design pattern with a simple wedding card story, easy TypeScript and Python examples, and a clear shallow vs deep copy demo.
Iterator Pattern: Visit Every Element, One Seat at a Time
Learn the Iterator pattern with a railway ticket checker story. Build a custom iterator in TypeScript, use for...of and generators, plus C# IEnumerable.
Mediator Pattern: The Control Tower That Stops the Chaos
Learn the Mediator pattern with an airport control tower story, simple TypeScript and C# code, MediatR examples, diagrams, tables and easy practice tasks.