Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

Design Patterns (GoF and modern)  Module 5 – Behavioral patterns: state and communication

Memento: snapshots that keep state private

Memento saves an object’s state in an opaque snapshot so it can be restored later: the originator, memento and caretaker roles, undo histories and save files.

  • Intermediate
  • 15 minutes
  • Examples run with Python 3.14.8, Pyodide 314.0.7 and Node.js 24.21.0
  • By MySmartCoPilot
Free intro, Pro: 3 of 15 minutes free
Free: How Memento works. With a pass: the other 8 sections and 3 interview answers, about 12 minutes.

What you will learn

  • Implement save and restore through an opaque memento that only the originator can read
  • Compare snapshot-based undo with command-based undo for memory and complexity
  • Limit history size and handle large states with keyframes and deltas
  • Save snapshots safely as versioned JSON instead of unsafe deserialisation

Before you start

What you’ll learn · Before you start

What you will learn

  • Implement save and restore through an opaque memento that only the originator can read
  • Compare snapshot-based undo with command-based undo for memory and complexity
  • Limit history size and handle large states with keyframes and deltas
  • Save snapshots safely as versioned JSON instead of unsafe deserialisation

Before you start

On this page

Press undo three times in a drawing app and the canvas must step back through exactly the three states it had, while the code that keeps those states should know nothing about how a canvas stores its shapes. Memento is the behavioural design pattern for that. The object being saved, the originator: In the Memento pattern, the object whose state is saved: it packs its state into a memento and is the only object that reads one again on restore., packs its state into a memento: An opaque snapshot of an object's state: the object that made it can restore itself from it, and anyone else can only store it and hand it back., and another object, the caretaker: In the Memento pattern, the object that keeps mementos, such as an undo history, and returns them on request without ever looking inside one., stores mementos and hands them back without ever looking inside one.

Undo is the classic use: an editor saves a memento before every change and restores the newest one when the user presses undo. Checkpoints in games, rolling back a half-finished form and the Cancel button of a settings dialog work the same way.

How Memento works

Suppose a drawing canvas keeps its shapes private, and the editor needs an undo history. If the history copied the canvas’s fields itself, it would depend on every one of them and break the day the canvas stored its shapes differently. With Memento the canvas makes the copy and the history only keeps it, so the canvas can change its insides at will:

Memento over time: the history asks the canvas for a snapshot, keeps it unread, and later passes it back for the canvas to read.History(caretaker)Canvas(originator)Snapshot(memento)1. save() before a change2. packs its private state3. returns the snapshot4. keeps it, never reads it5. restore(snapshot) on undo6. reads its state back

Save before a change, restore on undo

Text description of the diagram

Three participants stand in columns: the History (the caretaker), the Canvas (the originator) and the Snapshot (the memento). Time runs from top to bottom.

  1. Before a change, the history calls save() on the canvas.
  2. The canvas packs its private state into a new snapshot.
  3. The canvas returns the snapshot to the history.
  4. The history keeps the snapshot and never reads what is inside it.
  5. On undo, the history passes the snapshot back with restore(snapshot).
  6. The canvas reads its own state back out of the snapshot.

Only the canvas ever reads a snapshot. The history knows how many snapshots it holds and in which order, nothing more.

A memento therefore has two faces. The caretaker sees a narrow one: an object it can store, count and hand back, and nothing else. The originator sees a wide one: everything it needs to rebuild its state. The snapshot is taken before each change, because the state before the change is exactly what undo must bring back.

The program below plays three edits and three undos. Its caretaker is a plain list, which can keep snapshots in order and return them, and nothing more. It runs in 14 languages with one output, and each tab’s note says how that language stops the list’s owner from reading a snapshot:

One program · 14 languages A canvas, its opaque snapshots and three undos

Python 3.14 · main.py

from collections.abc import Callable
from dataclasses import dataclass


@dataclass(frozen=True)
class Shape:
    kind: str
    x: int
    y: int


class Canvas:
    """The originator: the only object that reads a snapshot."""

    class Snapshot:
        """The memento: private by convention."""

        __slots__ = ("_shapes",)

        def __init__(self, shapes: tuple[Shape, ...]) -> None:
            self._shapes = shapes

    def __init__(self) -> None:
        self._shapes: tuple[Shape, ...] = ()

    def draw(self, kind: str, x: int, y: int) -> None:
        self._shapes += (Shape(kind, x, y),)

    def move_last(self, dx: int, dy: int) -> None:
        last = self._shapes[-1]
        self._shapes = (*self._shapes[:-1], Shape(last.kind, last.x + dx, last.y + dy))

    def save(self) -> "Canvas.Snapshot":
        return Canvas.Snapshot(self._shapes)

    def restore(self, snapshot: "Canvas.Snapshot") -> None:
        self._shapes = snapshot._shapes

    def describe(self) -> str:
        return ", ".join(f"{s.kind} at {s.x},{s.y}" for s in self._shapes) or "(empty)"


canvas = Canvas()
history: list[Canvas.Snapshot] = []  # the caretaker: it never looks inside a snapshot
edits: list[tuple[str, Callable[[], None]]] = [
    ("draw a circle", lambda: canvas.draw("circle", 10, 10)),
    ("draw a square", lambda: canvas.draw("square", 40, 10)),
    ("move the square", lambda: canvas.move_last(5, 5)),
]
for label, edit in edits:
    history.append(canvas.save())  # save before every change
    edit()
    print(f"{label}: {canvas.describe()}")
print(f"the history holds {len(history)} snapshots")
while history:
    canvas.restore(history.pop())
    print(f"undo: {canvas.describe()}")

In Python: Python has no private members, so the underscore of _shapes marks the snapshot's slot private by convention only. The shapes are frozen and kept in tuples, so a snapshot can share them with the canvas.

Recorded with CPython 3.14.8 on macOS 26 arm64. It runs in your browser with Pyodide 314.0.7 (CPython 3.14.2). To run it yourself: python3 main.py

JavaScript Node 24 · main.mjs

/** What each snapshot stands for, read only by Canvas. */
const contents = new WeakMap();

/** The originator: the only object that reads a snapshot. */
class Canvas {
  #shapes = [];

  draw(kind, x, y) {
    this.#shapes = [...this.#shapes, { kind, x, y }];
  }

  moveLast(dx, dy) {
    const last = this.#shapes.at(-1);
    this.#shapes = [...this.#shapes.slice(0, -1), { ...last, x: last.x + dx, y: last.y + dy }];
  }

  save() {
    const snapshot = Object.freeze({}); // the memento: an empty token
    contents.set(snapshot, this.#shapes);
    return snapshot;
  }

  restore(snapshot) {
    this.#shapes = contents.get(snapshot);
  }

  describe() {
    return this.#shapes.map((s) => `${s.kind} at ${s.x},${s.y}`).join(", ") || "(empty)";
  }
}

const canvas = new Canvas();
const history = []; // the caretaker: it never looks inside a snapshot
const edits = [
  ["draw a circle", () => canvas.draw("circle", 10, 10)],
  ["draw a square", () => canvas.draw("square", 40, 10)],
  ["move the square", () => canvas.moveLast(5, 5)],
];
for (const [label, edit] of edits) {
  history.push(canvas.save()); // save before every change
  edit();
  console.log(`${label}: ${canvas.describe()}`);
}
console.log(`the history holds ${history.length} snapshots`);
while (history.length > 0) {
  canvas.restore(history.pop());
  console.log(`undo: ${canvas.describe()}`);
}

In JavaScript: A snapshot is an empty frozen object, and a WeakMap of the module holds what it stands for, so code outside the module cannot read it, and the shapes go once the snapshot does.

Recorded with Node.js 24.21.0 LTS (V8 13.6) on macOS 26 arm64. It runs in your browser with QuickJS (quickjs-emscripten 0.32.0). To run it yourself: node main.mjs

Java 25 LTS · Main.java

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

public class Main {
  public static void main(String[] args) {
    var canvas = new Canvas();
    List<Canvas.Memento> history = new ArrayList<>(); // the caretaker: it never looks inside one
    List<String> labels = List.of("draw a circle", "draw a square", "move the square");
    List<Runnable> edits =
        List.of(
            () -> canvas.draw("circle", 10, 10),
            () -> canvas.draw("square", 40, 10),
            () -> canvas.moveLast(5, 5));
    for (int i = 0; i < edits.size(); i++) {
      history.add(canvas.save()); // save before every change
      edits.get(i).run();
      System.out.println(labels.get(i) + ": " + canvas.describe());
    }
    System.out.println("the history holds " + history.size() + " snapshots");
    while (!history.isEmpty()) {
      canvas.restore(history.remove(history.size() - 1));
      System.out.println("undo: " + canvas.describe());
    }
  }
}

record Shape(String kind, int x, int y) {}

/** The originator: the only object that reads a snapshot. */
final class Canvas {
  interface Memento {}

  /** The memento. */
  private record Snapshot(List<Shape> shapes) implements Memento {}

  private final List<Shape> shapes = new ArrayList<>();

  void draw(String kind, int x, int y) {
    shapes.add(new Shape(kind, x, y));
  }

  void moveLast(int dx, int dy) {
    Shape last = shapes.remove(shapes.size() - 1);
    shapes.add(new Shape(last.kind(), last.x() + dx, last.y() + dy));
  }

  Memento save() {
    return new Snapshot(List.copyOf(shapes)); // a copy, which later edits cannot reach
  }

  void restore(Memento memento) {
    shapes.clear();
    shapes.addAll(((Snapshot) memento).shapes());
  }

  String describe() {
    if (shapes.isEmpty()) return "(empty)";
    return shapes.stream()
        .map(s -> s.kind() + " at " + s.x() + "," + s.y())
        .collect(Collectors.joining(", "));
  }
}

In Java: Snapshot is a private record nested in Canvas, so main, in another top-level class, sees only the empty Memento interface. save() stores List.copyOf(shapes), a copy that later edits cannot reach.

Recorded with OpenJDK 25.0.4.1 LTS (Eclipse Temurin) on macOS 26 arm64. It runs in your browser with a TeaVM 0.16.0 build in QuickJS (quickjs-emscripten 0.32.0). To run it yourself: java Main.java

C++23 · main.cpp

#include <format>
#include <print>
#include <string>
#include <utility>
#include <vector>

struct Shape {
  std::string kind;
  int x, y;
};

// The memento: only its friend Canvas makes or reads one.
class Snapshot {
  friend class Canvas;
  explicit Snapshot(std::vector<Shape> shapes) : shapes_(std::move(shapes)) {}
  std::vector<Shape> shapes_;
};

// The originator: the only object that reads a snapshot.
class Canvas {
public:
  void draw(std::string kind, int x, int y) {
    shapes_.push_back({std::move(kind), x, y});
  }

  void move_last(int dx, int dy) {
    shapes_.back().x += dx;
    shapes_.back().y += dy;
  }

  Snapshot save() const { return Snapshot(shapes_); } // a copy of the shapes

  void restore(Snapshot snapshot) { shapes_ = std::move(snapshot.shapes_); }

  std::string describe() const {
    std::string text;
    for (const Shape &s : shapes_)
      text += std::format("{}{} at {},{}", text.empty() ? "" : ", ", s.kind,
                          s.x, s.y);
    return text.empty() ? "(empty)" : text;
  }

private:
  std::vector<Shape> shapes_;
};

int main() {
  Canvas canvas;
  std::vector<Snapshot>
      history; // the caretaker: it never looks inside a snapshot
  const std::pair<const char *, void (*)(Canvas &)> edits[] = {
      {"draw a circle", [](Canvas &c) { c.draw("circle", 10, 10); }},
      {"draw a square", [](Canvas &c) { c.draw("square", 40, 10); }},
      {"move the square", [](Canvas &c) { c.move_last(5, 5); }}};
  for (const auto &[label, edit] : edits) {
    history.push_back(canvas.save()); // save before every change
    edit(canvas);
    std::println("{}: {}", label, canvas.describe());
  }
  std::println("the history holds {} snapshots", history.size());
  while (!history.empty()) {
    canvas.restore(std::move(history.back()));
    history.pop_back();
    std::println("undo: {}", canvas.describe());
  }
}

In C++: Every member of Snapshot is private, its constructor included, and friend class Canvas is the one exception, so other code can store and move snapshots but can neither make nor read one.

Recorded with Apple clang 21.0.0 (C++23) and GCC 16.2.0 on macOS 26 arm64. It runs in your browser with Clang in WebAssembly (YoWASP) 22.0.0-git20542-10. To run it yourself: clang++ -std=c++23 -O2 -Wall -Wextra -Werror -pedantic -fsanitize=address,undefined main.cpp -o main && ./main

C# 14 · .NET 10 · app.cs

using System.Globalization;

CultureInfo.CurrentCulture = CultureInfo.InvariantCulture;

var canvas = new Canvas();
var history = new Stack<IMemento>(); // the caretaker: it never looks inside a snapshot
(string Label, Action Edit)[] edits =
[
    ("draw a circle", () => canvas.Draw("circle", 10, 10)),
    ("draw a square", () => canvas.Draw("square", 40, 10)),
    ("move the square", () => canvas.MoveLast(5, 5)),
];
foreach (var (label, edit) in edits)
{
    history.Push(canvas.Save()); // save before every change
    edit();
    Console.WriteLine($"{label}: {canvas.Describe()}");
}
Console.WriteLine($"the history holds {history.Count} snapshots");
while (history.Count > 0)
{
    canvas.Restore(history.Pop());
    Console.WriteLine($"undo: {canvas.Describe()}");
}

sealed record Shape(string Kind, int X, int Y);

interface IMemento
{
}

/// <summary>The originator: the only object that reads a snapshot.</summary>
sealed class Canvas
{
    // The memento.
    sealed record Snapshot(IReadOnlyList<Shape> Shapes) : IMemento;

    IReadOnlyList<Shape> shapes = [];

    public void Draw(string kind, int x, int y) => shapes = [.. shapes, new Shape(kind, x, y)];

    public void MoveLast(int dx, int dy)
    {
        var last = shapes[^1];
        shapes = [.. shapes.Take(shapes.Count - 1), last with { X = last.X + dx, Y = last.Y + dy }];
    }

    public IMemento Save() => new Snapshot(shapes);

    public void Restore(IMemento memento) => shapes = ((Snapshot)memento).Shapes;

    public string Describe() =>
        shapes.Count == 0
            ? "(empty)"
            : string.Join(", ", shapes.Select(s => $"{s.Kind} at {s.X},{s.Y}"));
}

In C#: Snapshot is a private record nested in Canvas, so the top-level code holds IMemento references, an interface with nothing in it.

Recorded with C# 14 on .NET 10.0.12 (SDK 10.0.401) on macOS 26 arm64. It runs in your browser with the .NET runtime for WebAssembly 10.0.12. To run it yourself: dotnet run app.cs

C23 · main.c

#include <stdio.h>
#include <stdlib.h>

typedef struct {
  const char *kind;
  int x, y;
} Shape;

// The originator: the only code that reads a snapshot.
typedef struct {
  Shape shapes[8];
  size_t count;
} Canvas;

// The memento: defined after main, so main cannot read one.
typedef struct Snapshot Snapshot;
static Snapshot *save(const Canvas *canvas);
static void restore(Canvas *canvas, Snapshot *snapshot);

static void draw(Canvas *canvas, const char *kind, int x, int y) {
  canvas->shapes[canvas->count++] = (Shape){kind, x, y};
}

static void move_last(Canvas *canvas, int dx, int dy) {
  canvas->shapes[canvas->count - 1].x += dx;
  canvas->shapes[canvas->count - 1].y += dy;
}

static void describe(const char *label, const Canvas *canvas) {
  printf("%s: ", label);
  for (size_t i = 0; i < canvas->count; i++) {
    const Shape *shape = &canvas->shapes[i];
    printf("%s%s at %d,%d", i > 0 ? ", " : "", shape->kind, shape->x, shape->y);
  }
  puts(canvas->count == 0 ? "(empty)" : "");
}

static void draw_circle(Canvas *canvas) { draw(canvas, "circle", 10, 10); }
static void draw_square(Canvas *canvas) { draw(canvas, "square", 40, 10); }
static void move_square(Canvas *canvas) { move_last(canvas, 5, 5); }

int main(void) {
  Canvas canvas = {.count = 0};
  Snapshot *history[8]; // the caretaker: it never looks inside a snapshot
  size_t saved = 0;
  const struct {
    const char *label;
    void (*apply)(Canvas *canvas);
  } edits[] = {{"draw a circle", draw_circle},
               {"draw a square", draw_square},
               {"move the square", move_square}};
  for (size_t i = 0; i < sizeof edits / sizeof edits[0]; i++) {
    history[saved++] = save(&canvas); // save before every change
    edits[i].apply(&canvas);
    describe(edits[i].label, &canvas);
  }
  printf("the history holds %zu snapshots\n", saved);
  while (saved > 0) {
    restore(&canvas, history[--saved]);
    describe("undo", &canvas);
  }
}

struct Snapshot {
  Canvas state;
};

static Snapshot *save(const Canvas *canvas) {
  Snapshot *snapshot = malloc(sizeof *snapshot);
  if (snapshot == nullptr) {
    abort();
  }
  snapshot->state = *canvas; // a copy, which later edits cannot reach
  return snapshot;
}

static void restore(Canvas *canvas, Snapshot *snapshot) {
  *canvas = snapshot->state;
  free(snapshot); // restoring uses the snapshot up
}

In C: Snapshot is declared before main and defined after it, so main can store and pass snapshot pointers while the compiler refuses any read of their fields; a header and a separate .c file give the same guarantee across files. restore() frees the snapshot it uses.

Recorded with Apple clang 21.0.0 (C23) and GCC 16.2.0 on macOS 26 arm64. It runs in your browser with Clang in WebAssembly (YoWASP) 22.0.0-git20542-10. To run it yourself: clang -std=c23 -O2 -Wall -Wextra -Werror -pedantic -fsanitize=address,undefined main.c -o main && ./main

PHP 8.5 · main.php

<?php

declare(strict_types=1);

final readonly class Shape
{
    public function __construct(public string $kind, public int $x, public int $y) {}
}

/** The memento: an empty token. */
final class Snapshot {}

/** The originator: the only object that reads a snapshot. */
final class Canvas
{
    /** @var list<Shape> */
    private array $shapes = [];

    /** @param WeakMap<Snapshot, list<Shape>> $contents what each snapshot stands for */
    public function __construct(private WeakMap $contents = new WeakMap()) {}

    public function draw(string $kind, int $x, int $y): void
    {
        $this->shapes[] = new Shape($kind, $x, $y);
    }

    public function moveLast(int $dx, int $dy): void
    {
        $last = array_pop($this->shapes);
        $this->shapes[] = new Shape($last->kind, $last->x + $dx, $last->y + $dy);
    }

    public function save(): Snapshot
    {
        $snapshot = new Snapshot();
        $this->contents[$snapshot] = $this->shapes; // arrays are values, so this is a copy
        return $snapshot;
    }

    public function restore(Snapshot $snapshot): void
    {
        $this->shapes = $this->contents[$snapshot];
    }

    public function describe(): string
    {
        $parts = array_map(fn(Shape $s) => "{$s->kind} at {$s->x},{$s->y}", $this->shapes);
        return $parts === [] ? '(empty)' : implode(', ', $parts);
    }
}

$canvas = new Canvas();
$history = []; // the caretaker: it never looks inside a snapshot
$edits = [
    'draw a circle' => fn() => $canvas->draw('circle', 10, 10),
    'draw a square' => fn() => $canvas->draw('square', 40, 10),
    'move the square' => fn() => $canvas->moveLast(5, 5),
];
foreach ($edits as $label => $edit) {
    $history[] = $canvas->save(); // save before every change
    $edit();
    echo "{$label}: {$canvas->describe()}\n";
}
echo 'the history holds ' . count($history) . " snapshots\n";
while ($history !== []) {
    $canvas->restore(array_pop($history));
    echo "undo: {$canvas->describe()}\n";
}

In PHP: A snapshot is an empty object, and Canvas keeps what it stands for in a WeakMap, a private property, so nothing outside can reach the shapes, which vanish with the snapshot.

Recorded with PHP 8.5.11 (CLI) on macOS 26 arm64. It runs in your browser with php-wasm 0.2.0 (PHP 8.5.2). To run it yourself: php main.php

Rust 1.99 · main.rs

mod canvas {
    #[derive(Clone)]
    pub struct Shape {
        kind: &'static str,
        x: i32,
        y: i32,
    }

    /// The memento.
    pub struct Snapshot {
        shapes: Vec<Shape>,
    }

    /// The originator: the only object that reads a snapshot.
    #[derive(Default)]
    pub struct Canvas {
        shapes: Vec<Shape>,
    }

    impl Canvas {
        pub fn draw(&mut self, kind: &'static str, x: i32, y: i32) {
            self.shapes.push(Shape { kind, x, y });
        }

        pub fn move_last(&mut self, dx: i32, dy: i32) {
            if let Some(last) = self.shapes.last_mut() {
                last.x += dx;
                last.y += dy;
            }
        }

        pub fn save(&self) -> Snapshot {
            Snapshot {
                shapes: self.shapes.clone(),
            }
        }

        pub fn restore(&mut self, snapshot: Snapshot) {
            self.shapes = snapshot.shapes;
        }

        pub fn describe(&self) -> String {
            let parts: Vec<String> = self
                .shapes
                .iter()
                .map(|s| format!("{} at {},{}", s.kind, s.x, s.y))
                .collect();
            if parts.is_empty() {
                "(empty)".to_string()
            } else {
                parts.join(", ")
            }
        }
    }
}

use canvas::{Canvas, Snapshot};

fn main() {
    let mut canvas = Canvas::default();
    // The caretaker: it never looks inside a snapshot.
    let mut history: Vec<Snapshot> = Vec::new();
    let edits: [(&str, fn(&mut Canvas)); 3] = [
        ("draw a circle", |c| c.draw("circle", 10, 10)),
        ("draw a square", |c| c.draw("square", 40, 10)),
        ("move the square", |c| c.move_last(5, 5)),
    ];
    for (label, edit) in edits {
        history.push(canvas.save()); // save before every change
        edit(&mut canvas);
        println!("{label}: {}", canvas.describe());
    }
    println!("the history holds {} snapshots", history.len());
    while let Some(snapshot) = history.pop() {
        canvas.restore(snapshot);
        println!("undo: {}", canvas.describe());
    }
}

In Rust: Snapshot's field is private to the canvas module, so main can store and move snapshots but the compiler refuses any code outside that reads one. restore takes the snapshot by value and uses it up.

Recorded with rustc 1.99.0 (edition 2024) on macOS 26 arm64. It runs in your browser with a WebAssembly build on the WASI runner (browser_wasi_shim 0.4.2). To run it yourself: rustc --edition 2024 -O main.rs && ./main

TypeScript 6.0 · main.ts

interface Shape {
  readonly kind: string;
  readonly x: number;
  readonly y: number;
}

type Snapshot = { readonly __brand: "Snapshot" }; // the memento: an empty token

/** What each snapshot stands for, read only by Canvas. */
const contents = new WeakMap<Snapshot, readonly Shape[]>();

/** The originator: the only object that reads a snapshot. */
class Canvas {
  #shapes: readonly Shape[] = [];

  draw(kind: string, x: number, y: number): void {
    this.#shapes = [...this.#shapes, { kind, x, y }];
  }

  moveLast(dx: number, dy: number): void {
    const last = this.#shapes.at(-1);
    if (last === undefined) return;
    this.#shapes = [...this.#shapes.slice(0, -1), { ...last, x: last.x + dx, y: last.y + dy }];
  }

  save(): Snapshot {
    const snapshot = Object.freeze({}) as Snapshot;
    contents.set(snapshot, this.#shapes);
    return snapshot;
  }

  restore(snapshot: Snapshot): void {
    this.#shapes = contents.get(snapshot) ?? [];
  }

  describe(): string {
    return this.#shapes.map((s) => `${s.kind} at ${s.x},${s.y}`).join(", ") || "(empty)";
  }
}

const canvas = new Canvas();
const history: Snapshot[] = []; // the caretaker: it never looks inside a snapshot
const edits: [string, () => void][] = [
  ["draw a circle", () => canvas.draw("circle", 10, 10)],
  ["draw a square", () => canvas.draw("square", 40, 10)],
  ["move the square", () => canvas.moveLast(5, 5)],
];
for (const [label, edit] of edits) {
  history.push(canvas.save()); // save before every change
  edit();
  console.log(`${label}: ${canvas.describe()}`);
}
console.log(`the history holds ${history.length} snapshots`);
for (let snapshot = history.pop(); snapshot !== undefined; snapshot = history.pop()) {
  canvas.restore(snapshot);
  console.log(`undo: ${canvas.describe()}`);
}

In TypeScript: The same WeakMap token as in JavaScript. The branded type Snapshot lets callers store snapshots, but no property of one tells them anything.

Recorded with TypeScript 6.0.3 (strict), run by Node.js 24.21.0 on macOS 26 arm64. It runs in your browser with QuickJS (quickjs-emscripten 0.32.0) after Sucrase 3.35.1. To run it yourself: npx -p typescript@6.0.3 tsc --noEmit --strict main.ts && node main.ts

Go 1.27 · main.go

package main

import (
	"fmt"
	"slices"
	"strings"
)

type Shape struct {
	Kind string
	X, Y int
}

// Snapshot is the memento.
type Snapshot struct{ shapes []Shape }

// Canvas is the originator: the only object that reads a snapshot.
type Canvas struct{ shapes []Shape }

func (c *Canvas) Draw(kind string, x, y int) { c.shapes = append(c.shapes, Shape{kind, x, y}) }

func (c *Canvas) MoveLast(dx, dy int) {
	last := &c.shapes[len(c.shapes)-1]
	last.X += dx
	last.Y += dy
}

// Save and Restore copy the slice, which MoveLast changes in place.
func (c *Canvas) Save() Snapshot     { return Snapshot{slices.Clone(c.shapes)} }
func (c *Canvas) Restore(s Snapshot) { c.shapes = slices.Clone(s.shapes) }

func (c *Canvas) Describe() string {
	parts := make([]string, len(c.shapes))
	for i, s := range c.shapes {
		parts[i] = fmt.Sprintf("%s at %d,%d", s.Kind, s.X, s.Y)
	}
	if len(parts) == 0 {
		return "(empty)"
	}
	return strings.Join(parts, ", ")
}

func main() {
	canvas := &Canvas{}
	var history []Snapshot // the caretaker: it never looks inside a snapshot
	edits := []struct {
		label string
		edit  func()
	}{
		{"draw a circle", func() { canvas.Draw("circle", 10, 10) }},
		{"draw a square", func() { canvas.Draw("square", 40, 10) }},
		{"move the square", func() { canvas.MoveLast(5, 5) }},
	}
	for _, e := range edits {
		history = append(history, canvas.Save()) // save before every change
		e.edit()
		fmt.Printf("%s: %s\n", e.label, canvas.Describe())
	}
	fmt.Printf("the history holds %d snapshots\n", len(history))
	for len(history) > 0 {
		canvas.Restore(history[len(history)-1])
		history = history[:len(history)-1]
		fmt.Println("undo:", canvas.Describe())
	}
}

In Go: Go hides unexported names per package, so in another package Snapshot's field would be unreadable; this one-file program can only state that. Save and Restore copy the slice, because MoveLast changes it in place.

Recorded with Go 1.27.1 on macOS 26 arm64. It runs in your browser with the Go compiler in WebAssembly 1.27.1. To run it yourself: go run main.go

Swift 6.4 · main.swift

struct Shape {
  let kind: String
  var x: Int
  var y: Int
}

/// The originator: the only object that reads a snapshot.
final class Canvas {
  /// The memento.
  struct Snapshot {
    fileprivate let shapes: [Shape]
  }

  private var shapes: [Shape] = []

  func draw(_ kind: String, x: Int, y: Int) {
    shapes.append(Shape(kind: kind, x: x, y: y))
  }

  func moveLast(dx: Int, dy: Int) {
    shapes[shapes.count - 1].x += dx
    shapes[shapes.count - 1].y += dy
  }

  func save() -> Snapshot { Snapshot(shapes: shapes) }  // arrays are values: this is a copy

  func restore(_ snapshot: Snapshot) { shapes = snapshot.shapes }

  func describe() -> String {
    shapes.isEmpty
      ? "(empty)" : shapes.map { "\($0.kind) at \($0.x),\($0.y)" }.joined(separator: ", ")
  }
}

let canvas = Canvas()
var history: [Canvas.Snapshot] = []  // the caretaker: it never looks inside a snapshot
let edits: [(String, () -> Void)] = [
  ("draw a circle", { canvas.draw("circle", x: 10, y: 10) }),
  ("draw a square", { canvas.draw("square", x: 40, y: 10) }),
  ("move the square", { canvas.moveLast(dx: 5, dy: 5) }),
]
for (label, edit) in edits {
  history.append(canvas.save())  // save before every change
  edit()
  print("\(label): \(canvas.describe())")
}
print("the history holds \(history.count) snapshots")
while let snapshot = history.popLast() {
  canvas.restore(snapshot)
  print("undo: \(canvas.describe())")
}

In Swift: Access control works per file and module, so fileprivate hides the shapes from other files only; in this one file the opacity is stated, not enforced. Arrays are values, so a snapshot is a copy.

Recorded with Apple Swift 6.4 on macOS 26 arm64. It runs in your browser with a WebAssembly build on the WASI runner (browser_wasi_shim 0.4.2). To run it yourself: swiftc -O main.swift -o main && ./main

Ruby 4.0 · main.rb

# frozen_string_literal: true

Shape = Data.define(:kind, :x, :y)

# The originator: the only object that reads a snapshot.
class Canvas
  # The memento: a private constant.
  Snapshot = Data.define(:shapes)
  private_constant :Snapshot

  def initialize
    @shapes = [].freeze
  end

  def draw(kind, x_pos, y_pos)
    @shapes = [*@shapes, Shape.new(kind, x_pos, y_pos)].freeze
  end

  def move_last(delta_x, delta_y)
    last = @shapes.last
    @shapes = [*@shapes[...-1], last.with(x: last.x + delta_x, y: last.y + delta_y)].freeze
  end

  def save = Snapshot.new(@shapes)

  def restore(snapshot)
    @shapes = snapshot.shapes
  end

  def describe
    return '(empty)' if @shapes.empty?

    @shapes.map { |shape| "#{shape.kind} at #{shape.x},#{shape.y}" }.join(', ')
  end
end

canvas = Canvas.new
history = [] # the caretaker: it never looks inside a snapshot
edits = {
  'draw a circle' => -> { canvas.draw('circle', 10, 10) },
  'draw a square' => -> { canvas.draw('square', 40, 10) },
  'move the square' => -> { canvas.move_last(5, 5) }
}
edits.each do |label, edit|
  history.push(canvas.save) # save before every change
  edit.call
  puts "#{label}: #{canvas.describe}"
end
puts "the history holds #{history.size} snapshots"
until history.empty?
  canvas.restore(history.pop)
  puts "undo: #{canvas.describe}"
end

In Ruby: private_constant hides the name Canvas::Snapshot outside the class, but any holder of a snapshot could still call shapes on it, so the opacity is a convention, as in Python.

Recorded with CRuby 4.0.7 on macOS 26 arm64. It runs in your browser with ruby.wasm 2.10.1 (Ruby 4.0.0). To run it yourself: ruby main.rb

Kotlin 2.4 · Main.kt

data class Shape(
    val kind: String,
    val x: Int,
    val y: Int,
)

interface Memento

/** The originator: the only object that reads a snapshot. */
class Canvas {
    /** The memento. */
    private class Snapshot(
        val shapes: List<Shape>,
    ) : Memento

    private var shapes = listOf<Shape>() // read-only, so a snapshot can share it

    fun draw(
        kind: String,
        x: Int,
        y: Int,
    ) {
        shapes = shapes + Shape(kind, x, y)
    }

    fun moveLast(
        dx: Int,
        dy: Int,
    ) {
        val last = shapes.last()
        shapes = shapes.dropLast(1) + last.copy(x = last.x + dx, y = last.y + dy)
    }

    fun save(): Memento = Snapshot(shapes)

    fun restore(memento: Memento) {
        shapes = (memento as Snapshot).shapes
    }

    fun describe(): String {
        val text = shapes.joinToString(", ") { "${it.kind} at ${it.x},${it.y}" }
        return text.ifEmpty { "(empty)" }
    }
}

fun main() {
    val canvas = Canvas()
    val history = ArrayDeque<Memento>() // the caretaker: it never looks inside a snapshot
    val edits =
        listOf(
            "draw a circle" to { canvas.draw("circle", 10, 10) },
            "draw a square" to { canvas.draw("square", 40, 10) },
            "move the square" to { canvas.moveLast(5, 5) },
        )
    for ((label, edit) in edits) {
        history.addLast(canvas.save()) // save before every change
        edit()
        println("$label: ${canvas.describe()}")
    }
    println("the history holds ${history.size} snapshots")
    while (history.isNotEmpty()) {
        canvas.restore(history.removeLast())
        println("undo: ${canvas.describe()}")
    }
}

In Kotlin: Snapshot is a private nested class behind the public Memento interface, so code outside Canvas can neither name the class nor read its shapes.

Recorded with Kotlin 2.4.20 on Java 25.0.4.1 on macOS 26 arm64. It runs in your browser with a WebAssembly build on the WASI runner (browser_wasi_shim 0.4.2). To run it yourself: kotlinc Main.kt -include-runtime -d main.jar && java -jar main.jar

Dart 3.13 · main.dart

typedef Shape = ({String kind, int x, int y});

/// The memento.
final class Snapshot {
  Snapshot._(this._shapes);

  final List<Shape> _shapes;
}

/// The originator: the only object that reads a snapshot.
final class Canvas {
  List<Shape> _shapes = const [];

  void draw(String kind, int x, int y) {
    _shapes = List.unmodifiable([..._shapes, (kind: kind, x: x, y: y)]);
  }

  void moveLast(int dx, int dy) {
    final last = _shapes.last;
    final moved = (kind: last.kind, x: last.x + dx, y: last.y + dy);
    _shapes = List.unmodifiable([..._shapes.take(_shapes.length - 1), moved]);
  }

  Snapshot save() => Snapshot._(_shapes);

  void restore(Snapshot snapshot) => _shapes = snapshot._shapes;

  String describe() => _shapes.isEmpty
      ? '(empty)'
      : _shapes.map((s) => '${s.kind} at ${s.x},${s.y}').join(', ');
}

void main() {
  final canvas = Canvas();
  final history =
      <Snapshot>[]; // the caretaker: it never looks inside a snapshot
  final edits = <(String, void Function())>[
    ('draw a circle', () => canvas.draw('circle', 10, 10)),
    ('draw a square', () => canvas.draw('square', 40, 10)),
    ('move the square', () => canvas.moveLast(5, 5)),
  ];
  for (final (label, edit) in edits) {
    history.add(canvas.save()); // save before every change
    edit();
    print('$label: ${canvas.describe()}');
  }
  print('the history holds ${history.length} snapshots');
  while (history.isNotEmpty) {
    canvas.restore(history.removeLast());
    print('undo: ${canvas.describe()}');
  }
}

In Dart: Privacy is per library, that is per file, so _shapes is hidden from other libraries. Snapshot._ is a private constructor, so code in other libraries cannot make a snapshot either.

Recorded with Dart 3.13.5 on macOS 26 arm64. It runs in your browser with a WebAssembly build by dart2wasm 3.13.5. To run it yourself: dart run main.dart

What every language printed

draw a circle: circle at 10,10
draw a square: circle at 10,10, square at 40,10
move the square: circle at 10,10, square at 45,15
the history holds 3 snapshots
undo: circle at 10,10, square at 40,10
undo: circle at 10,10
undo: (empty)

A real undo history has more decisions to make, and each has a name:

  • Snapshot-based undo stores a memento of the whole state before every change.
  • Command-based undo stores each change together with a way to reverse it, instead of the state.
  • Immutable state makes snapshots cheap, because a new snapshot can share every part that did not change.
  • A bounded history keeps only the newest mementos and forgets the oldest.
  • Keyframes with deltas store a full memento now and then, and only the changes in between.
  • A save file is a memento written to disk, and loading one brings its own security rules.

Checkpoint

Check: The originator, the memento and the caretaker

  1. Question 1 of 2 In the canvas program of the 14 tabs, which part is the caretaker?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: The history list, which stores the snapshots and hands them back without reading them

    The canvas is the originator, each snapshot is a memento, and the list that keeps them is the caretaker. A plain list is enough for that role, because the caretaker needs to know nothing about a snapshot.

  2. Question 2 of 2 The editor calls save() after each edit instead of before it. What does the first undo bring back?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: The state after the last edit, so the first undo changes nothing on the screen

    Undo must restore the state before the change, so the snapshot is taken before the change. Saving afterwards makes every undo one step late.

Each language keeps the memento opaque in its own way Pro: opens with a Pro, Premium or Ultimate pass

In this part:

  • Snapshots or commands: undo memory, measured
  • Bounding the history: a cap, keyframes and deltas
  • Saving snapshots without running code
  • When not to use Memento
  • Practise: undo for a sign-up wizard
    • Undo for a sign-up wizard, with a capped history and JSON saves
  • Interview questions
    • A photo editor keeps a full copy of the image as a memento before every edit. Users editing 4,000 × 3,000 photos run out of memory after about 20 edits. Estimate what the history costs, and redesign it so that 100 undo steps fit in a fixed budget.
    • A game writes its save files with Python's pickle, and players have started sharing save files online. What is the risk, and how do you move to a safe format without breaking the saves players already have?
    • You are designing undo for a collaborative text editor whose documents reach 10 MB. Do you use mementos or commands? Defend the choice, including what undo should do when another user's edit lands between your edit and your undo.
  • Key takeaways

1 diagram · 3 runnable examples (Python) · 1 exercise with sample tests · 3 interview answers · about 31 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

Terms in this lesson

caretaker
In the Memento pattern, the object that keeps mementos, such as an undo history, and returns them on request without ever looking inside one.
memento
An opaque snapshot of an object's state: the object that made it can restore itself from it, and anyone else can only store it and hand it back.
originator
In the Memento pattern, the object whose state is saved: it packs its state into a memento and is the only object that reads one again on restore.

References

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.