← Figma Interview Insights

Figma·Software Engineer·Onsite - System Design / Architecture·Senior

SeniorPrefer not to say
May 2026

Summary

Figma system design round focused entirely on a document-editing layer with undo/redo semantics. The problem kept expanding, which I wasn't fully ready for, but it was a genuinely interesting problem to work through.

Questions Asked (5)

Q1

Design a document-editing layer that supports applying edits and undoing them. Implement an API with apply(Operation op), undo(), and getText().

System DesignAlgorithms & Data StructuresAPI & Integrations
Author's notes

Started with a stack-based approach, two stacks, one for history and one for redo.

Create a free account to read the full note

AI HintsAI Generated

Suggested Approach

Clarify requirements (operation types, undo depth, concurrency) and then propose a design using a command pattern with an undo stack. Discuss data structures for efficient text editing (e.g., piece table or rope) and how to handle memory and performance trade-offs.

Pro tip: Mention that undo should be implemented via inverse operations or snapshots, and highlight the importance of idempotency and atomicity for operations. Also, consider discussing how to handle collaborative editing scenarios, as Figma deals with real-time collaboration.

1. Clarify Requirements

Ask about the types of operations (insert, delete, replace), undo depth, whether redo is needed, and concurrency requirements. Confirm if the API should support batching or transactions.

2. Design the API and Data Model

Define the Operation interface with apply and invert methods. Choose a data structure for the document (e.g., piece table, rope, or gap buffer) that balances memory and performance for edits.

3. Implement Undo Mechanism

Use a stack to store inverse operations or snapshots. For each apply, push the inverse onto the undo stack; undo pops and applies the inverse. Discuss memory implications and potential optimizations like operation coalescing.

4. Handle Edge Cases and Concurrency

Address concurrent edits (e.g., using operational transforms or CRDTs) and ensure thread safety if needed. Discuss how to handle undo/redo across multiple users in a collaborative setting.

5. Analyze Trade-offs and Optimize

Compare approaches (e.g., inverse operations vs. snapshots) in terms of time/space complexity. Suggest optimizations like lazy deletion or periodic compaction.

Key Points to Mention

  • Command pattern with inverse operations for undo
  • Data structures: piece table, rope, or gap buffer for efficient text editing
  • Undo stack management and memory considerations (e.g., limiting undo depth)
  • Handling concurrent edits with OT or CRDTs for collaborative scenarios
  • API design: apply(Operation op), undo(), getText() with clear contracts
  • Time and space complexity of operations and undo

AI-generated suggestions, not part of the candidate's original notes. May be inaccurate — verify before relying on them.

Q2

Extend the design to support transactional batching: beginBatch(), apply(...), commitBatch(), where undo() must revert an entire committed batch as a single atomic unit.

System DesignTechnical Trade-offsAPI & Integrations
Author's notes

This is where it got interesting.

Create a free account to read the full note

AI HintsAI Generated

Suggested Approach

Start by clarifying the existing undo/redo design and the atomicity requirements, then propose a batch as a single composite command that groups multiple operations. Explain how beginBatch/apply/commitBatch manage a pending buffer, and how commitBatch pushes one atomic entry onto the undo stack. Finally, discuss trade-offs around memory, performance, and failure handling.

Pro tip: Emphasize that atomicity means either all operations in the batch are applied and undone together, or none are—so you need to handle partial failures during commit and ensure undo reverts the entire batch as one unit. Also mention that batching can improve performance by reducing undo stack churn and enabling coalesced updates.

1. Clarify requirements and existing design

Ask about the current undo/redo implementation, expected batch sizes, and whether nested batches or concurrent batches are needed. Confirm that undo() must revert the entire batch atomically.

2. Design the batch API and state management

Define beginBatch() to start a pending batch, apply(...) to buffer operations without executing them, and commitBatch() to atomically apply all buffered operations and push a single composite command onto the undo stack.

3. Implement atomic commit and undo

During commitBatch(), execute all operations in order; if any fails, roll back already-applied operations to maintain atomicity. For undo(), revert the entire batch by undoing each operation in reverse order as a single unit.

4. Address edge cases and trade-offs

Discuss handling of nested batches, empty batches, and errors during apply or commit. Weigh memory overhead of storing batch operations versus performance gains from reduced undo stack entries.

5. Summarize and invite feedback

Recap the design, highlight how it meets atomicity and performance goals, and ask if the interviewer wants to explore alternative approaches or optimizations.

Key Points to Mention

  • Composite command pattern: treat a batch as a single command that encapsulates multiple operations.
  • Atomicity: ensure all operations in a batch are applied and undone together, with rollback on failure.
  • Pending buffer: store operations during the batch and apply them only on commit to avoid partial state.
  • Undo stack integration: push one entry per batch instead of per operation to simplify undo/redo.
  • Performance trade-offs: batching reduces undo stack size and overhead but increases memory for buffered operations.
  • Error handling: define behavior for failures during apply or commit, including rollback and cleanup.

AI-generated suggestions, not part of the candidate's original notes. May be inaccurate — verify before relying on them.

Q3

How would you optimize batch undo for very large batches containing millions of small operations, in terms of both time and memory?

System DesignAlgorithms & Data StructuresTechnical Trade-offs
Author's notes

This is the part I fumbled a bit.

Create a free account to read the full note

AI HintsAI Generated

Suggested Approach

Start by clarifying the constraints: what defines a 'small operation', the expected undo frequency, and whether undo must be immediate or can be asynchronous. Then propose a layered solution: compress the undo log using delta encoding and run-length encoding, and process undo in chunks with lazy evaluation to bound memory and time.

Pro tip: Emphasize that the best optimization often comes from preventing the need to store millions of individual operations in the first place—by coalescing operations at write time or using a persistent data structure with structural sharing.

1. Clarify requirements and constraints

Ask about the size of operations, memory limits, latency requirements, and whether undo can be batched or streamed. This ensures your solution targets the real bottleneck.

2. Choose a compact representation

Use delta encoding, run-length encoding, or a persistent data structure (e.g., immutable tree with path copying) to store the undo log efficiently, reducing memory from O(n) to O(changes).

3. Process undo in chunks with lazy evaluation

Instead of applying all operations at once, undo in fixed-size chunks or use lazy iterators to avoid loading the entire log into memory. This bounds peak memory and allows incremental progress.

4. Optimize time with indexing and parallelism

Build an index (e.g., skip list or B-tree) to quickly locate operations to undo, and parallelize independent undo operations where possible to reduce wall-clock time.

5. Discuss trade-offs and alternatives

Compare approaches: e.g., eager vs. lazy undo, in-memory vs. on-disk storage, and the trade-off between undo speed and memory usage. Mention when to coalesce operations at write time.

Key Points to Mention

  • Delta encoding and run-length encoding to compress repetitive operations
  • Persistent data structures (e.g., immutable trees) with structural sharing to avoid storing full copies
  • Chunked or streaming undo to bound memory usage and allow incremental progress
  • Lazy evaluation: deferring undo work until necessary, possibly using a command pattern with inverse operations
  • Indexing (e.g., B-tree, skip list) to quickly locate operations and avoid full scans
  • Parallelism and concurrency: undoing independent operations in parallel, with synchronization where needed
  • Trade-offs between time and memory: e.g., precomputing undo snapshots vs. recomputing on the fly

AI-generated suggestions, not part of the candidate's original notes. May be inaccurate — verify before relying on them.

Q4

Implement redo() that works correctly for both single edits and committed batches, and explain how new edits should invalidate the redo stack.

System DesignAlgorithms & Data Structures
Author's notes

Pretty standard redo stack invalidation: any new apply() or commitBatch() clears the redo stack.

Create a free account to read the full note

AI HintsAI Generated

Suggested Approach

Start by clarifying the data model: a command pattern with undo/redo stacks, where each command encapsulates an edit or a batch. Then describe how redo() pops from the redo stack and applies the command, and how new edits clear the redo stack to maintain linear history. Finally, discuss handling batches as composite commands and edge cases like empty stacks.

Pro tip: Emphasize that redo should be symmetric to undo: if undo pushes onto the redo stack, redo should push onto the undo stack. Also, mention that clearing the redo stack on new edits is standard but can be configurable (e.g., for branching history) — showing awareness of product trade-offs.

1. Clarify requirements and data model

Ask if batches are atomic and if redo should work across sessions. Propose a command pattern with undo and redo stacks, where each command has execute() and undo() methods.

2. Design redo for single edits

Explain that redo() checks if the redo stack is non-empty, pops the top command, calls execute() (or redo()), and pushes it onto the undo stack.

3. Extend to committed batches

Represent a batch as a composite command containing a list of sub-commands. redo() on a batch executes all sub-commands in order, and undo() reverses them in reverse order.

4. Handle new edits invalidating redo

When a new edit is performed, clear the redo stack because the history has branched. This ensures redo only replays edits that were undone and not superseded.

5. Discuss edge cases and optimizations

Mention empty stack handling, memory management (e.g., limiting stack size), and potential for merging commands or using persistent data structures for efficiency.

Key Points to Mention

  • Command pattern with execute() and undo() methods for each edit.
  • Separate undo and redo stacks to track history.
  • Composite command for batches: execute sub-commands in order, undo in reverse.
  • New edits clear the redo stack to maintain linear history.
  • Redo operation is symmetric to undo: pop from redo, execute, push to undo.
  • Edge cases: empty redo stack, batch atomicity, and memory constraints.

AI-generated suggestions, not part of the candidate's original notes. May be inaccurate — verify before relying on them.

Q5

Describe how you'd handle edge cases like overlapping range edits and cursor bookkeeping within this editing layer.

System DesignData ModelingTechnical Trade-offs
Author's notes

Ran out of steam a little here near the end.

Create a free account to read the full note

AI HintsAI Generated

Suggested Approach

Start by clarifying the scope and constraints of the editing layer, then propose a robust data model that handles overlapping edits and cursor bookkeeping through normalization and transformation. Walk through concrete edge cases and trade-offs, emphasizing correctness, performance, and collaboration scenarios.

Pro tip: Demonstrate awareness of operational transformation (OT) or CRDTs, but focus on how you'd validate and test edge cases—this shows you prioritize reliability over buzzwords.

1. Clarify requirements and constraints

Ask about the editing layer's scope: single-user vs. collaborative, real-time vs. batch, and performance requirements. This ensures your solution addresses the right problems.

2. Define a robust data model

Propose representing edits as operations with ranges and metadata, and cursors as positions that can be transformed. Consider using a normalized structure to avoid ambiguity.

3. Handle overlapping edits

Explain strategies like operational transformation or conflict-free replicated data types (CRDTs) to merge concurrent edits. Discuss how to detect and resolve overlaps deterministically.

4. Manage cursor bookkeeping

Describe how to update cursor positions when edits occur, using transformation functions that adjust positions based on insertions/deletions. Ensure cursors remain stable and intuitive.

5. Test and validate edge cases

Outline a testing strategy with unit tests for overlapping edits, cursor movements, and undo/redo. Mention property-based testing to cover complex scenarios.

Key Points to Mention

  • Operational transformation (OT) and CRDTs for conflict resolution
  • Data structures for representing ranges and positions (e.g., intervals, ropes, or piece tables)
  • Cursor transformation rules for insertions, deletions, and replacements
  • Undo/redo stack integration and its impact on cursor state
  • Performance considerations for large documents and frequent edits
  • Collaboration scenarios: multiple users, latency, and consistency models

AI-generated suggestions, not part of the candidate's original notes. May be inaccurate — verify before relying on them.