Skip to content
StackPractices
intermediate By Mathias Paulenko

Memento Pattern

Capture and restore an object's internal state without violating encapsulation. A behavioral design pattern for undo/redo.

Topics: design

Note: This guide follows English-language naming conventions and terminology standards common in international development teams. Examples use English identifiers and comments to maximize compatibility across codebases and tooling.

Memento Pattern

Overview

The Memento Pattern is a behavioral design pattern that lets you save and restore the previous state of an object without revealing its internal structure. It is the foundation of undo/redo functionality in applications like text editors, drawing programs, and game state management.

When to Use

Use the Memento Pattern when:

  • You need to implement undo and redo functionality
  • You want to save checkpoints of an object’s state
  • You must preserve encapsulation and not expose internal state directly
  • State restoration should be possible without the client knowing the object’s internals

Solution

Python

class EditorMemento:
    def __init__(self, content: str, cursor: int):
        self._content = content
        self._cursor = cursor

    @property
    def content(self) -> str:
        return self._content

    @property
    def cursor(self) -> int:
        return self._cursor

class TextEditor:
    def __init__(self):
        self._content = ""
        self._cursor = 0

    def type(self, text: str):
        self._content = self._content[:self._cursor] + text
        self._cursor += len(text)

    def save(self) -> EditorMemento:
        return EditorMemento(self._content, self._cursor)

    def restore(self, memento: EditorMemento):
        self._content = memento.content
        self._cursor = memento.cursor

    @property
    def content(self) -> str:
        return self._content

# Usage with history
class History:
    def __init__(self):
        self._history = []

    def push(self, memento):
        self._history.append(memento)

    def pop(self):
        if not self._history:
            return None
        return self._history.pop()

editor = TextEditor()
history = History()

history.push(editor.save())
editor.type("Hello ")
history.push(editor.save())
editor.type("World!")

print(editor.content)  # Hello World!

editor.restore(history.pop())
print(editor.content)  # Hello

editor.restore(history.pop())
print(editor.content)  # (empty)

JavaScript

class EditorMemento {
  constructor(content, cursor) {
    this.content = content;
    this.cursor = cursor;
  }
}

class TextEditor {
  constructor() {
    this._content = "";
    this._cursor = 0;
  }

  type(text) {
    this._content = this._content.slice(0, this._cursor) + text;
    this._cursor += text.length;
  }

  save() {
    return new EditorMemento(this._content, this._cursor);
  }

  restore(memento) {
    this._content = memento.content;
    this._cursor = memento.cursor;
  }

  get content() {
    return this._content;
  }
}

// Usage
class History {
  constructor() {
    this.history = [];
  }

  push(memento) {
    this.history.push(memento);
  }

  pop() {
    return this.history.pop();
  }
}

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

history.push(editor.save());
editor.type("Hello ");
history.push(editor.save());
editor.type("World!");

console.log(editor.content); // Hello World!

editor.restore(history.pop());
console.log(editor.content); // Hello

Java

public class EditorMemento {
    private final String content;
    private final int cursor;

    public EditorMemento(String content, int cursor) {
        this.content = content;
        this.cursor = cursor;
    }

    public String getContent() { return content; }
    public int getCursor() { return cursor; }
}

public class TextEditor {
    private String content = "";
    private int cursor = 0;

    public void type(String text) {
        content = content.substring(0, cursor) + text;
        cursor += text.length();
    }

    public EditorMemento save() {
        return new EditorMemento(content, cursor);
    }

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

    public String getContent() { return content; }
}

// Usage with history
import java.util.ArrayDeque;
import java.util.Deque;

public class History {
    private final Deque<EditorMemento> stack = new ArrayDeque<>();

    public void push(EditorMemento memento) {
        stack.push(memento);
    }

    public EditorMemento pop() {
        return stack.isEmpty() ? null : stack.pop();
    }
}

// Demo
TextEditor editor = new TextEditor();
History history = new History();

history.push(editor.save());
editor.type("Hello ");
history.push(editor.save());
editor.type("World!");

System.out.println(editor.getContent());

editor.restore(history.pop());
System.out.println(editor.getContent());

Explanation

The Memento Pattern has three roles:

  • Originator (TextEditor): The object whose state needs to be saved
  • Memento (EditorMemento): An immutable snapshot of the originator’s state
  • Caretaker (History): Manages mementos (when to save, when to restore) without accessing their contents

The key benefit is that the memento’s internal state is opaque to the caretaker, preserving encapsulation.

Variants

VariantDescriptionUse Case
Full SnapshotStores entire object stateSmall objects, infrequent snapshots
Delta/IncrementalStores only changed fieldsLarge objects, frequent snapshots
Serializable MementoUses serialization for deep copyingComplex object graphs
Command + MementoCommands store mementos for undoTransaction systems, editors

What Works

  • Keep mementos immutable after creation to prevent accidental tampering
  • Limit memento lifetime — large histories consume substantial memory
  • Consider serialization for complex object graphs, but be aware of performance costs
  • Implement a memento interface that only exposes state restoration methods to the originator
  • Use delta mementos for large objects where only a few fields change

Common Mistakes

  • Exposing the memento’s internal state to the caretaker, breaking encapsulation
  • Storing too many full snapshots, causing excessive memory usage
  • Not handling memento versioning when the originator’s structure changes over time
  • Forgetting to validate mementos before restoration (corrupted or incompatible snapshots)
  • Allowing originators to modify mementos after creation, causing unpredictable undo behavior

Frequently Asked Questions

Q: How is Memento different from Prototype? A: Prototype creates a new object by copying an existing one. Memento saves an object’s state so it can be restored later. Prototype is about duplication; Memento is about time-travel.

Q: Can I use serialization instead of Memento? A: Yes, but serialization is often slower and less controlled. Memento gives you fine-grained control over what state is saved and how it is restored.

Is this pattern suitable for small projects?

For small projects with few components, this pattern may add unnecessary complexity. Start simple and introduce the pattern when you feel the pain it solves.

How does this pattern compare to alternatives?

Each pattern makes different trade-offs. Review the variants table above and consider your specific constraints: team size, performance requirements, and future scaling plans.

Can I partially apply this pattern?

Yes. Many teams adopt patterns incrementally. Start with the core idea and add sophistication as needed. The pattern is a guide, not a strict blueprint.