Requirements
Implement a Document class whose state is a set of layers; each layer has an id and a map of property key → value.
Part 1 — apply & undo
apply(id, property_name, value)— set a property on a specific layer (creating the layer if needed). A laterapplyto the same(id, property_name)overwrites the previous value; other properties on that layer are untouched.undo()— revert the most recent operation, restoring the exact prior state.- Some variants also ask for
get_layer_by_id(id)(or equivalent read accessor).
Part 2 — batches
commit_batch()— group the operations since the previous commit into one unit. Aftercommit_batch,undo()must revert the whole batch as a single step (not oneapplyat a time). Applies still take effect immediately; the commit only changes undo granularity.- A common follow-up is to make batch-undo space-efficient (do not store every intermediate state).
Part 3 — redo
redo()— re-apply the most recently undone operation or batch. Supporting redo means undo cannot simply discard history.
Edge cases interviewers probe
undo()when there is nothing left to undo.- Coalescing repeated writes:
apply(1,'color','red')thenapply(1,'color','blue')within a batch should not require storing both — store only what is needed to reverse the batch.
Examples
doc = Document()
doc.apply(1, "color", "green") # Layer 1: {color: green}
doc.apply(2, "shape", "triangle") # Layer 1: {color: green}, Layer 2: {shape: triangle}
doc.apply(1, "color", "blue") # Layer 1: {color: blue}, Layer 2: {shape: triangle}
doc.undo() # Layer 1: {color: green}, Layer 2: {shape: triangle}
Batch + redo variant:
apply(Layer(1, {"color": "green"}))
apply(Layer(2, {"shape": "triangle", "color": "blue"}))
apply(Layer(1, {"color": "pink"}))
commit_batch()
apply(Layer(1, {"color": "blue"}))
apply(Layer(1, {"color": "white"}))
commit_batch()
# State: Layer 1 {color: white}, Layer 2 {shape: triangle, color: blue}
# Undo the last batch -> Layer 1 {color: pink}, Layer 2 {shape: triangle, color: blue}
Notes
- This is the most reliably recurring Figma coding question across years and roles (SWE and MLE phone screens). Interviewers supply the test harness; getting the provided tests to pass on the first run is the bar.
- Conflicting reports on batch semantics — clarify before coding. Candidates have hit two framings: (a) on
commit_batch, store the oldest value per touched property and ignore intermediate states, using a delimiter/marker to bound the batch on the undo stack; (b) store aDeque<List<Operation>>of operations per batch. Both can pass, but interviewers push on the space-efficient version — ask which inverse representation they want before committing to a data structure. - The spec as written by the interviewer often differs slightly from community prep (simpler or differently-worded conditions). Re-read carefully; over-fitting to a remembered version has actively hurt candidates.
- Language traps to avoid: in Go,
cannot assign to struct field in map(mutate via a temp struct or use pointers); in C++, do not store the address of a range-loop local in a member. - Treat
undo/redoas two stacks (or a history list with a cursor); a per-operation inverse (old value, or "property did not exist") makes reversal O(1) and avoids snapshotting the whole document.
Preparation
- Implement the full
apply/undo/commit_batch/redoclass from scratch in your interview language and run it against your own assert-based test harness until first-run green. - Practice the inverse-operation representation: for each
apply, record what is needed to undo it (previous value, or a tombstone if the key was absent), and rehearse coalescing repeated writes within a batch.

