{} The Go Reference

Behavioral pattern · Gang of Four · Intermediate

Memento

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

Behavioral Intermediate ⏱ 3 min read Complete

🎮 Analogy

A video game save point captures your entire state — position, inventory, health — in one blob. Hours later you reload it and you’re exactly where you were. You never see the save file’s internals; you just save and restore. That blob is a memento.

The problem

You want undo, checkpoints, or rollback — which means capturing an object’s state and restoring it later. But you don’t want the thing doing the saving (the caretaker) to rummage through the object’s private fields. Memento puts the snapshot in an opaque object: the originator creates and reads it; everyone else just holds it.

Structure

classDiagram
class Editor {
  -text string
  +Save() memento
  +Restore(memento)
}
class memento {
  -text string
}
class History {
  -stack memento[]
  +Push(memento)
  +Pop() memento
}
Editor ..> memento : creates & reads
History o--> memento : stores (opaque)

Idiomatic Go

memento has unexported fields, so History (the caretaker) can stack snapshots without reading them — only the Editor can. Edit and Run:

memento.go — editable & runnable
package main

import "fmt"

// memento: an opaque snapshot. Unexported fields keep it private.
type memento struct{ text string }

// Originator.
type Editor struct{ text string }

func (e *Editor) Type(s string)     { e.text += s }
func (e *Editor) Text() string      { return e.text }
func (e *Editor) Save() memento     { return memento{text: e.text} }
func (e *Editor) Restore(m memento) { e.text = m.text }

// Caretaker: keeps history, treats mementos as opaque tokens.
type History struct{ stack []memento }

func (h *History) Push(m memento) { h.stack = append(h.stack, m) }
func (h *History) Pop() (memento, bool) {
if len(h.stack) == 0 {
	return memento{}, false
}
m := h.stack[len(h.stack)-1]
h.stack = h.stack[:len(h.stack)-1]
return m, true
}

func main() {
ed := &Editor{}
hist := &History{}

ed.Type("Hello")
hist.Push(ed.Save()) // checkpoint
ed.Type(", World")
hist.Push(ed.Save()) // checkpoint
ed.Type("!!!")

fmt.Println("now:   ", ed.Text()) // Hello, World!!!

if m, ok := hist.Pop(); ok {
	ed.Restore(m)
	fmt.Println("undo 1:", ed.Text()) // Hello, World
}
if m, ok := hist.Pop(); ok {
	ed.Restore(m)
	fmt.Println("undo 2:", ed.Text()) // Hello
}
}

🐹 Encapsulation by package, undo by Command

Go has no private keyword — encapsulation is per package. Unexported memento fields mean the caretaker can hold snapshots but not peek inside, which is exactly the pattern’s intent. Memento pairs naturally with Command for undo (a command snapshots state before acting) and shares the Prototype deep-copy caveat: if your state has slices or maps, the snapshot must copy them, not alias them.

Three ways to undo

Memento is one of three strategies for “go back,” and they trade memory for complexity:

ApproachStoresBest when
Memento (full snapshot)a complete state copy per checkpointstate is small, or restore must be exact and simple
Diff / deltaonly what changed at each stepstate is large but edits are local
Command Undohow to reverse each actionactions are cleanly invertible (toggle, add/remove)

In Go, a snapshot can be a small struct of unexported fields (as above), or — for arbitrary graphs — a gob/json round-trip (slow, drops unexported fields, but generic). The opacity that defines Memento comes for free from Go’s package-level encapsulation.

Pitfalls

⚠️ Full snapshots can get expensive

Saving the whole state on every checkpoint is simple but memory-hungry for large objects or long histories. When that bites, store diffs (what changed) instead of full copies, cap the history depth, or use Command’s Undo to reverse actions rather than restore snapshots.

When to use it — and when not

✅ Reach for it when

  • You need undo/redo, checkpoints, or snapshots of an object's state.
  • You want to roll back to a previous state after a failed operation (transactions).
  • The state should be saved without exposing the object's internals to the saver.

⛔ Think twice when

  • The state is huge — full snapshots cost too much memory; store diffs or use Command to reverse actions.
  • The object is trivial — saving and restoring a field by hand is simpler.

Check your understanding

Score: 0 / 5

1. What does Memento preserve while saving state?

The memento captures internal state in an object whose contents only the originator can read, so the caretaker can store/restore it without seeing inside.

2. In Go, what enforces the memento's opacity?

Go's encapsulation is at the package level. With unexported memento fields, the caretaker can hold mementos but can't inspect or tamper with them.

3. Which pattern commonly pairs with Memento for undo?

Command + Memento is the classic undo combo: the command captures a snapshot (or enough state) before acting, so Undo can restore it.

4. Your editor state holds a []string. What must Save() do for a correct memento?

A slice field is a header sharing a backing array. If the memento just copies the header, editing the live document also corrupts the snapshot. Memento inherits Prototype's deep-copy caveat: duplicate slices/maps (slices.Clone, etc.) when snapshotting.

5. When should you store diffs (or use Command's Undo) instead of full mementos?

Full snapshots are simple but O(state size) per checkpoint. For big documents or deep undo stacks, store just what changed (a diff) or reverse the action via Command.Undo, which often needs far less memory than a whole snapshot.

Comments

Sign in with GitHub to join the discussion.