Design document editor with undo/redo and batching
Company: Figma
Role: Software Engineer
Category: Coding & Algorithms
Difficulty: medium
Interview Round: Technical Screen
Quick Answer: The question evaluates design of mutable text-editing layers and undo/redo semantics, including transactional batching, performance optimization for very large operation histories, and handling of overlapping range edits and cursor bookkeeping within the Coding & Algorithms domain.
Part 1: Basic Document Editing with Undo
Constraints
- 0 <= len(commands) <= 10000
- 0 <= document length after any command <= 100000
- For delete and replace, 0 <= start <= end <= current document length
- For insert, 0 <= index <= current document length
- undo on an empty history is a no-op
Examples
Input: ([] ,)
Expected Output: []
Explanation: No commands produce no output.
Input: ([['apply','insert','0','hello'],['get'],['apply','insert','5',' world'],['get'],['undo'],['get'],['undo'],['get'],['undo'],['get']],)
Expected Output: ['hello', 'hello world', 'hello', '', '']
Explanation: Two inserts are undone in reverse order. The final extra undo has no effect.
Hints
- For every applied operation, store the inverse operation needed to undo it.
- For a replace, the inverse must remember the exact text that was overwritten.
Part 2: Transactional Batches with Atomic Undo
Constraints
- 0 <= len(commands) <= 10000
- 0 <= document length after any command <= 100000
- Batch nesting is not used
- For every edit, the provided range or index is valid for the current document state
- undo on an empty history is a no-op
Examples
Input: ([['apply','insert','0','abcdef'],['begin'],['apply','replace','1','4','X'],['apply','insert','2','Y'],['commit'],['get'],['undo'],['get'],['undo'],['get']],)
Expected Output: ['aXYef', 'abcdef', '']
Explanation: The committed batch changes abcdef to aXYef. One undo restores the whole batch, and the next undo removes the initial insert.
Input: ([['apply','insert','0','A'],['begin'],['apply','insert','1','B'],['apply','insert','2','C'],['commit'],['get'],['undo'],['get'],['apply','insert','1','D'],['get'],['undo'],['get']],)
Expected Output: ['ABC', 'A', 'AD', 'A']
Explanation: The BC inserts are undone together, while the later D insert is a separate undoable unit.
Hints
- A history entry can be either one inverse operation or a list of inverse operations.
- To undo a batch, apply its inverse operations in reverse order.
Part 3: Compress a Large Batch into One Undo Patch
Constraints
- 0 <= len(initial_text) <= 100000
- 0 <= len(operations) <= 10000
- 0 <= document length after any operation <= 200000
- For every edit, the provided range or index is valid for the current document state
- The compressed undo metadata should depend on the size of the net changed region, not directly on the number of operations
Examples
Input: ('abcdef', [['replace','1','4','X'],['insert','2','Y']])
Expected Output: ['aXYef', '1', '4', 'XY', 'bcd', 'abcdef']
Explanation: The net effect is replacing bcd with XY.
Input: ('hello', [])
Expected Output: ['hello', '5', '5', '', '', 'hello']
Explanation: No edits produce an empty patch at the end of the document.
Hints
- After computing the final text, skip the equal prefix and equal suffix shared by the initial and final strings.
- The inverse of one replace patch only needs the original middle segment and the length of the replacement.
Part 4: Undo and Redo for Single Edits and Batches
Constraints
- 0 <= len(commands) <= 10000
- 0 <= document length after any command <= 100000
- For every edit, the provided range or index is valid for the current document state
- undo and redo on empty stacks are no-ops
- Any user apply clears the redo stack, including an apply inside a new batch
Examples
Input: ([['apply','insert','0','abc'],['get'],['undo'],['get'],['redo'],['get'],['redo'],['get']],)
Expected Output: ['abc', '', 'abc', 'abc']
Explanation: The second redo has no effect because there is no remaining redo history.
Input: ([['apply','insert','0','abcdef'],['begin'],['apply','replace','1','4','X'],['apply','insert','2','Y'],['commit'],['get'],['undo'],['get'],['redo'],['get'],['undo'],['get']],)
Expected Output: ['aXYef', 'abcdef', 'aXYef', 'abcdef']
Explanation: The committed batch is undone and redone atomically.
Hints
- Store each history unit with both its forward operations and inverse operations.
- Undo applies inverse operations in reverse order; redo applies forward operations in original order.