ExampleDifficulty: Intermediate2 min

Memento Example: Save Points in a Game

The game saves level, health and inventory at a checkpoint, and the save menu only lists them.

A game's save points are mementos kept by a caretaker that holds many at once and lets the player choose. This card shows the game, which saves and loads itself, and the save menu, which lists the saves by name and time without being able to read the level, health or inventory inside them. It is written in TypeScript.

The participants in the game save example
ParticipantGame class
OriginatorGame, which holds the level, health and inventory
MementoSavePoint, which shows only a name and a time
CaretakerSaveMenu, which lists save points and returns the one the player picks
ts
type SaveData = { readonly level: number; readonly health: number; readonly inventory: readonly string[] };

class SavePoint {
  constructor(readonly name: string, readonly createdAt: string) {}
}

class Game {
  #saves = new WeakMap<SavePoint, SaveData>();
  private level = 1;
  private health = 100;
  private inventory: string[] = [];

  play(level: number, health: number, found: string): void {
    this.level = level;
    this.health = health;
    this.inventory.push(found);
    this.status("played");
  }

  save(name: string, createdAt: string): SavePoint {
    const point = new SavePoint(name, createdAt);
    this.#saves.set(point, { level: this.level, health: this.health, inventory: [...this.inventory] });
    return point;
  }

  load(point: SavePoint): void {
    const data = this.#saves.get(point);
    if (!data) {
      return;
    }
    this.level = data.level;
    this.health = data.health;
    this.inventory = [...data.inventory];
    this.status(`loaded "${point.name}"`);
  }

  private status(event: string): void {
    console.log(`${event}: level ${this.level}, health ${this.health}, items [${this.inventory.join(", ")}]`);
  }
}

class SaveMenu {
  private readonly points: SavePoint[] = [];

  add(point: SavePoint): void {
    this.points.push(point);
  }

  list(): void {
    for (const point of this.points) {
      console.log(`  ${point.createdAt}  ${point.name}`);
    }
  }

  pick(name: string): SavePoint | undefined {
    return this.points.find((point) => point.name === name);
  }
}

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

game.play(2, 80, "key");
menu.add(game.save("Before the bridge", "10:05"));
game.play(3, 35, "map");
menu.add(game.save("Castle gate", "10:40"));
game.play(4, 0, "torch");

console.log("save menu:");
menu.list();
const chosen = menu.pick("Before the bridge");
if (chosen) {
  game.load(chosen);
}
OutputRan with node:22-slim (Node 22.23.1), TypeScript 5.9.3
played: level 2, health 80, items [key]
played: level 3, health 35, items [key, map]
played: level 4, health 0, items [key, map, torch]
save menu:
  10:05  Before the bridge
  10:40  Castle gate
loaded "Before the bridge": level 2, health 80, items [key]
The save data lives in the game's private `#saves`; a `SavePoint` is a label.

The player saves before the bridge and at the castle gate, then loses all their health on level 4. The menu lists both saves by time and name, and loading "Before the bridge" puts the game back on level 2 with 80 health and only the key.

Two things differ from a plain undo history. The caretaker keeps every memento and lets the user choose one, rather than returning the last. And the memento carries a public part, the name and time the menu shows, beside a private part only the game can read. Both share the core of the pattern: only the originator reads the saved state.

The inventory is copied on save and again on load. Without the copies, items picked up after a save would appear in it, because the saved list and the game's list would be one object.