designpattern.site

Memento Pattern

Capture and externalize an object's internal state so it can be restored later, without violating encapsulation.

Overview

Every non-trivial editing tool needs an undo button. Every wizard-style form needs a "go back" option. Every game needs a save-and-load mechanism. All of these share a common need: preserve a snapshot of some object's state at a point in time, and restore it later.

The Memento pattern is a behavioral design pattern that addresses this need. It lets you capture an object's full internal state into a separate "memento" object, store that memento outside the originating object, and later feed it back to restore the original state — all without exposing the object's private fields to the outside world.

The central insight is that only the originating object should know how to pack and unpack its own state. Mementos are opaque to everyone else — they carry data but expose no structure.

Problem & Motivation

Imagine building a text editor. The editor holds a document object that tracks the current text content, cursor position, and selected range. You want to support unlimited undo.

The naive approach is to save a copy of the entire document state into a list on every keystroke. That works, but it means the undo manager must know the internal layout of document — what fields exist, which are serializable, which can be safely deep-copied. Now the undo manager is tightly coupled to the document's internals. When you add a new field to document, you must also update the undo manager.

Alternatively, you could expose all fields via getters so the undo manager can read and replay them. But now every internal detail is part of the public API, making future refactors painful.

The Memento pattern resolves this by giving the document the responsibility to create and consume its own snapshots. The undo manager holds a stack of opaque memento objects — it cannot read them, it can only pass them back to the document on request.

Class Diagram

Loading diagram...

The three roles are:

  • Originator — the object whose state needs to be saved. It creates mementos and restores its state from them.
  • Memento — the snapshot. It stores a copy of the originator's internal state. It is opaque to everyone except the originator.
  • Caretaker — manages the memento stack (the undo history). It knows when to save and when to restore, but never inspects the contents of a memento.

Implementation

The implementations below model a TextEditor that supports save-and-undo of its content and cursor position. All four versions produce the same behavior: typing, saving checkpoints, and restoring a previous state.

// Memento — opaque to the Caretaker
class EditorMemento {
  private readonly content: string;
  private readonly cursor: number;

  constructor(content: string, cursor: number) {
    this.content = content;
    this.cursor = cursor;
  }

  // Only the Originator should call these
  getContent(): string { return this.content; }
  getCursor(): number { return this.cursor; }
}

// Originator
class TextEditor {
  private content: string = "";
  private cursor: number = 0;

  type(text: string): void {
    this.content += text;
    this.cursor = this.content.length;
  }

  save(): EditorMemento {
    return new EditorMemento(this.content, this.cursor);
  }

  restore(memento: EditorMemento): void {
    this.content = memento.getContent();
    this.cursor = memento.getCursor();
  }

  toString(): string {
    return `"${this.content}" (cursor: ${this.cursor})`;
  }
}

// Caretaker
class History {
  private stack: EditorMemento[] = [];
  private editor: TextEditor;

  constructor(editor: TextEditor) {
    this.editor = editor;
  }

  backup(): void {
    this.stack.push(this.editor.save());
  }

  undo(): void {
    const memento = this.stack.pop();
    if (memento) {
      this.editor.restore(memento);
    }
  }
}

// Usage
const editor = new TextEditor();
const history = new History(editor);

history.backup();
editor.type("Hello");
history.backup();
editor.type(", World");

console.log(editor.toString()); // "Hello, World" (cursor: 13)

history.undo();
console.log(editor.toString()); // "Hello" (cursor: 5)

history.undo();
console.log(editor.toString()); // "" (cursor: 0)

Memento Immutability

A memento should always be immutable once created. If the caretaker can modify a memento's contents, the saved state is no longer trustworthy. In Python, use a frozen dataclass or __slots__ with no setters. In Java, make all fields final. In Go, keep fields unexported. In TypeScript, use readonly or freeze the object after construction.

Real-World Examples

Text editors and IDEs — Every editor from VS Code to Vim implements undo/redo on top of a state-snapshot mechanism. The editor buffer is the originator, each keystroke checkpoints a memento, and the undo stack is the caretaker. Some editors use a richer variant (operation-based undo / CRDT), but the conceptual role of Memento is identical.

Game save systems — A role-playing game saves the player's position, inventory, health, and quest flags as a serialized snapshot. The save-file manager holds a list of save slots (caretakers) without parsing what is inside them. Loading a slot feeds the snapshot back to the game engine (the originator), which unpacks it.

Form wizards and multi-step UIs — Web wizards that let users navigate back and forth between steps often snapshot the form state at each step boundary. Each step's data is stored in a memento so navigating backward restores the exact values the user entered, rather than forcing them to re-enter data.

Database transaction rollback — Relational databases use a write-ahead log (WAL) that captures the before-image of each changed row before a transaction modifies it. If the transaction is rolled back, the engine replays the before-images — essentially restoring from mementos. This is Memento applied at infrastructure scale.

Pros and Cons

Advantages

Preserves encapsulation — the originator's internal state is never exposed to the caretaker. Private fields stay private; only the originator knows how to read and write its own snapshot.

Clean undo/redo — gives a principled, reusable structure for undo history. The caretaker only needs to manage a stack; it does not need to understand the originator at all.

Snapshot isolation — because each memento is an independent, immutable copy, restoring one snapshot does not affect others on the stack.

Easy to extend — adding new fields to the originator requires updating only save() and restore(). The caretaker requires no changes.

Disadvantages

Memory consumption — saving state on every change can be expensive. A large document editor checkpointing on every keystroke stores hundreds of full copies of the document. Strategies like delta compression or checkpointing only on explicit actions can mitigate this.

Deep-copy complexity — for originators that hold references to other objects, save() must perform a deep copy to avoid the memento sharing mutable references with the live object. Shallow copies silently break the pattern.

Lifecycle management — the caretaker must decide when to discard old mementos. Without a cap on history depth, the stack grows without bound.

Not suitable for large object graphs — if the originator's state includes large collections, open file handles, or network connections, capturing a full snapshot is impractical or impossible.

When to Use / When to Avoid

Use Memento when:

  • You need undo/redo and want to keep the originator's internals private.
  • State snapshots are reasonably small or checkpoints are infrequent (on explicit user actions rather than every change).
  • The originator's state can be fully represented as a plain value (no open handles, no live network connections).
  • You want a clean separation: the originator owns serialization logic, the caretaker owns history management.

Avoid Memento when:

  • The originator's state is very large and changes frequently — consider command-based undo (storing inverse operations) instead.
  • The state graph contains circular references or live resources that cannot be deep-copied.
  • You only need undo for a subset of fields — expose only those fields via a dedicated snapshot type rather than capturing everything.
  • The language or framework already provides change-tracking (e.g., database ORM dirty-tracking, reactive state frameworks with built-in time travel) — don't re-implement what the platform gives you.

Command — the most common alternative for undo/redo. Instead of saving the full state before each change, Command records the operation and its inverse. Command is more memory-efficient when changes are incremental; Memento is simpler when the full state is small. Many systems combine both: commands for fine-grained changes, periodic Memento checkpoints as recovery points.

Prototype — cloning an object is the simplest way to create a memento. If your originator implements clone() / copy(), you can use that as the save() mechanism directly. The distinction is intent: Prototype is about object creation, Memento is about state history.

Iterator — when you want to replay a sequence of past states rather than just restore the most recent one, you can wrap the memento stack in an iterator. Stepping forward and backward through history is exactly what an iterator provides.

Serializer / Snapshot — in distributed systems, the Memento concept maps to event sourcing and CQRS snapshots. An aggregate's state is periodically snapshotted (a Memento) and the event log is truncated. The aggregate (originator) owns how to produce and consume the snapshot; the event store (caretaker) owns where it lives.

Design Tip

When memory pressure is a concern, consider a hybrid approach: store full Memento snapshots at coarse intervals (e.g., every 10 actions) and use the Command pattern for individual steps within an interval. To undo, roll back to the nearest checkpoint memento and replay the remaining commands. This gives you the encapsulation benefits of Memento without paying for a full copy on every change.

On this page