Design a Text Editor
Insert/delete, cursor movement, and undo/redo via Command + Memento, with a gap-buffer document model.
Problem Statement
Design the object model for a single-user text editor that supports a mutable document buffer, a cursor, insert/delete operations, clipboard commands, and undo/redo.
The core interview challenge is not drawing pixels. It is designing a safe editing core where every mutating user action is encapsulated as a command, the document can restore a previous snapshot, and undo/redo stacks remain correct even as users type, delete, cut, paste, and format text.
Business context
Text editors power IDEs, note apps, ticket systems, and collaborative documents. A small editor exposes a surprisingly rich LLD surface: mutation history, cursor semantics, clipboard behavior, formatting metadata, and change notifications.
Interviewers use this problem to see whether you can separate the document model from the application shell, choose the right behavioral patterns, and protect undo/redo invariants. A strong answer makes each edit reversible, clears redo on new edits, and keeps memory growth under control.
Functional Requirements
Maintain a mutable document buffer and a cursor position between 0 and the document length.
Support inserting text at the cursor and advancing the cursor after the inserted text.
Support deleting text from the cursor and keeping the cursor at the deletion point.
Support cursor movement by absolute position with bounds validation.
Support undo and redo for mutating operations using two stacks.
Support clipboard operations: copy a range, cut from the cursor, and paste at the cursor.
Allow character formatting metadata to be attached without duplicating repeated style objects.
Notify interested listeners after document changes so UI widgets, autosave, or metrics can react.
Non-Functional Requirements
Correct undo/redo semantics
Every mutating command must capture enough state to undo itself. A new user edit after undo must clear the redo stack so history stays linear and predictable.
Fast interactive editing
Small documents can use a simple buffer, but the design should admit a gap buffer, rope, or piece table for production-scale documents without changing command APIs.
Memory discipline
Snapshots and style metadata can grow quickly. The base design snapshots the document for clarity and uses flyweight styles to avoid repeated formatting objects.
Extensibility
New commands such as replace, move line, indent, or apply format should implement the same Command interface and plug into the same history manager.
Separation of concerns
The document owns text and cursor invariants; commands own reversible operations; the editor orchestrates stacks and clipboard; observers are boundary notifications.
Requirement Clarification
QIs this a single-user or collaborative editor?
Assume single-user for the base design. Collaboration is a major extension that needs operational transforms or CRDTs and a different conflict model.
QDo undo and redo cover cursor movement and copy?
Only mutating operations are recorded in the base design: insert, delete, cut, and paste. Cursor movement and copy update local state but do not enter the undo stack.
QShould cut and paste be first-class commands?
Cut is modelled as clipboard assignment followed by a delete command; paste is an insert command using clipboard content. The mutating document part remains command-driven.
QHow rich is formatting?
Use a lightweight CharacterStyle object for font, size, bold, and italic. The editor can later add paragraph styles or ranges without changing the undo/redo contract.
QDo snapshots need to be full copies?
Full snapshots are acceptable for the interview because they make Memento easy to reason about. Production editors often switch to inverse operations, piece tables, or compressed history.
UML Class Diagram
Sequence Diagram
Entity Identification
Document
The editable aggregate. Owns the text buffer, cursor bounds, range operations, formatting spans, and the private memento used to restore a previous state.
TextEditor
Application facade over the editing core. Converts user actions into commands, manages undo/redo stacks, stores clipboard text, and notifies observers after changes.
Command
Common interface for reversible mutations. Every edit supports execute, undo, and a human-readable name for history displays.
InsertCommand
Encapsulates inserting text at the current cursor with a style. It saves a document memento before the mutation and restores it on undo.
DeleteCommand
Encapsulates deleting text from the current cursor. It captures the pre-delete memento and remembers the removed text for diagnostics or command history.
CharacterStyle
Immutable flyweight for repeated formatting attributes. Identical font, size, bold, and italic combinations are shared through a cache.
EditorListener
Observer interface for UI, autosave, status bar, or telemetry subscribers that need to react whenever editor-visible state changes.
Clipboard
A small editor-owned text slot. Copy updates it without history; cut updates it and performs a delete command; paste inserts its content through an insert command.
Design Patterns Used
Every mutating operation is a Command object with execute and undo. The editor history stores command objects, not ad hoc lambdas or switch cases.
Document.save returns an opaque snapshot of text, cursor, and styles. Commands keep the snapshot but cannot mutate its internals, preserving document encapsulation.
CharacterStyle.of shares immutable formatting objects for repeated style combinations, so thousands of characters can reference the same style instance.
EditorListener decouples the editing core from UI refresh, autosave, analytics, or status-bar updates. Subscribers react to changes without becoming part of command execution.
Step-by-Step Design
1Define the document as the owner of text and cursor invariants
Keep buffer mutation in Document. It validates cursor positions, slices ranges, inserts at the cursor, deletes from the cursor, and never exposes the mutable buffer directly.
public void moveCursor(int position) { if (position < 0 || position > buffer.length()) { throw new IllegalArgumentException("cursor out of bounds"); } cursor = position; }2Represent each mutation as a command
A command object captures the target document and the operation parameters. The editor does not need to know whether the command inserts, deletes, formats, or replaces text.
public interface Command { void execute(); void undo(); String name(); }3Use mementos for simple and reliable undo
Before executing, each command asks Document for a memento. Undo restores that snapshot. This is easy to explain and keeps command code small.
public void execute() { before = document.save(); document.insert(text, style); } public void undo() { document.restore(before); }4Centralize history updates in the editor
TextEditor is the only class that pushes undo, pushes redo, or clears redo. This prevents each command from duplicating fragile stack logic.
5Make clipboard operations reuse commands
copy only stores text. cut stores text then executes a delete command. paste executes an insert command. This keeps history behavior consistent with typing and deleting.
6Share repeated style metadata with a flyweight
Formatting metadata is immutable and cacheable. Multiple spans can point at the same CharacterStyle instance instead of storing repeated font and weight fields per character.
public static CharacterStyle of(String font, int size, boolean bold, boolean italic) { String key = font + "|" + size + "|" + bold + "|" + italic; return CACHE.computeIfAbsent(key, ignored -> new CharacterStyle(font, size, bold, italic)); }7Notify observers after visible state changes
Observers are called after successful mutations, undo, redo, cursor movement, copy, and cut. The editor sends action, text, and cursor so UI components can refresh without querying internals.
Complete Java Implementation
Explanation of Every Class
CharacterStyle
Immutable flyweight for formatting. The static of method normalizes inputs, builds a cache key, and returns a shared instance for repeated font, size, bold, and italic combinations.
Document
Owns the text buffer, cursor, style spans, and nested Memento. It exposes safe operations for insert, delete, slice, save, and restore while hiding mutable internals from commands.
Command
The reversible mutation contract. The editor can execute, undo, redo, and label commands without knowing their concrete operation type.
InsertCommand
Stores target document, inserted text, chosen style, and the pre-execution memento. execute inserts at the current cursor; undo restores the saved snapshot.
DeleteCommand
Stores target document and delete length. It saves the document memento before deletion, removes text from the cursor, and restores the memento on undo.
TextEditor
Facade and history manager. It translates user actions to commands, maintains undo/redo stacks, clears redo after new edits, manages clipboard text, and notifies listeners.
Main
Runnable demo that types text, deletes a word, undoes, copies, pastes, undoes again, and redoes. The listener output makes command flow and cursor movement visible.
Dry Run
Sample input
Initial document is empty. Actions: type Hello, type ** world**, move cursor to 6, delete 5 characters, undo, copy world, move to end, paste, undo, redo.
| Step | Action | Buffer | Cursor | Undo stack | Redo stack |
|---|---|---|---|---|---|
| 1 | type Hello | Hello | 5 | [Insert Hello] | [] |
| 2 | type space plus world | Hello world | 11 | [Insert world, Insert Hello] | [] |
| 3 | move cursor to 6 | Hello world | 6 | [Insert world, Insert Hello] | [] |
| 4 | delete 5 | Hello | 6 | [Delete world, Insert world, Insert Hello] | [] |
| 5 | undo | Hello world | 6 | [Insert world, Insert Hello] | [Delete world] |
| 6 | copy world and paste at end | Hello worldworld | 16 | [Paste world, Insert world, Insert Hello] | [] |
| 7 | undo then redo paste | Hello worldworld | 16 | [Paste world, Insert world, Insert Hello] | [] |
Step 5 moves the delete command from undo to redo after restoring the document memento. Step 6 clears redo because paste is a new edit, then records paste as another insert command.
Complexity Analysis
| Operation | Time | Space | Note |
|---|---|---|---|
| insert | O(n + s) | O(n) | StringBuilder may shift text after the cursor; the memento stores a document snapshot. s is the number of style spans adjusted. |
| delete | O(n + s) | O(n) | Deletion shifts remaining text and stores the pre-delete memento for undo. |
| copy | O(k) | O(k) | Copies k characters into the clipboard and does not affect undo history. |
| cut | O(n + k + s) | O(n + k) | Copies k characters to the clipboard and executes a delete command with a memento. |
| paste | O(n + k + s) | O(n) | Executes an insert command for k clipboard characters and clears redo. |
| undo / redo | O(n + s) | O(1) extra | Restores a saved memento or re-executes a command; the command objects already hold history state. |
The reference implementation favors clarity over optimal text editing data structures. In production, the Document internals can change to a gap buffer, rope, or piece table while Command and TextEditor remain stable.
Extensibility
Replace command
Implement ReplaceCommand as delete plus insert behind one memento so undo restores the exact prior state in a single history step.
Format command
Add ApplyStyleCommand that changes style spans over a range. It can reuse CharacterStyle flyweights and the same document memento mechanism.
Macro command
Introduce a composite command that groups multiple commands and exposes one execute and one undo operation for recorded macros.
Alternative buffer
Replace StringBuilder with a gap buffer, piece table, or rope. Commands target Document methods, so callers do not change.
Persistent history
Serialize command metadata or memento deltas so an editor session can recover unsaved history after a crash.
Alternative Designs
Inverse-operation commands
Instead of full document mementos, each command records the exact inverse: inserted range for insert, removed text for delete, and old styles for formatting.
Tradeoffs
Uses less memory for large documents but makes each command harder to implement and easier to get wrong around cursor and style edge cases.
Piece table document
Store original text and append-only added text, then represent the document as a list of pieces. Inserts and deletes adjust piece descriptors rather than moving a large array.
Tradeoffs
Excellent for editors and undo, but more complex than a StringBuilder model and harder to explain quickly in an LLD interview.
Event-sourced history
Persist every command as an event and rebuild document state by replaying events from an initial snapshot.
Tradeoffs
Great for audit and crash recovery, but replay cost and event versioning add production complexity.
UI-driven undo manager
Let the UI controller manage undo and redo stacks directly while document methods mutate text.
Tradeoffs
Simple at first, but stack rules spread across UI code and domain commands become harder to test in isolation.
Common Mistakes
- ×
Letting each button handler mutate the document directly instead of routing mutations through Command objects.
- ×
Forgetting to clear the redo stack after a new edit following undo.
- ×
Recording copy or pure cursor movement in undo history, causing surprising undo behavior.
- ×
Exposing the mutable StringBuilder or mutable style span list to callers.
- ×
Implementing undo as string reinsert/delete only and losing cursor position or formatting state.
- ×
Creating a separate formatting object for every character instead of sharing immutable styles.
- ×
Not validating cursor and range bounds, leading to hidden index errors in command execution.
Follow-up Interview Questions
QWhy use both Command and Memento instead of only one pattern?
Command captures the user action and history entry. Memento captures document state without exposing internals. Together they keep stack management clean and undo restoration safe.
QHow do you reduce memory usage for very large files?
Move from full snapshots to inverse operations, deltas, checkpointed mementos, or a piece table. Keep the public Command and Document APIs stable.
QWhat happens to redo after undo followed by typing?
Redo is cleared. Typing creates a new branch in history, and the simple editor maintains a linear history instead of a branching timeline.
QHow would you support grouped undo for typing a whole word?
Coalesce adjacent insert commands by time window, cursor adjacency, and style match, or wrap them in a macro command that appears as one undo entry.
QHow would collaborative editing change this design?
Local command history is no longer enough. You need operation transformation or CRDTs, remote cursors, conflict handling, and per-client undo semantics.
QWhere should autosave live?
As an observer or subscriber to editor changes. It should not be embedded inside Command or Document, because persistence is a boundary concern.
Production Considerations
Large document data structure
Use a gap buffer for simple local editing, a piece table for persistent undo-friendly storage, or a rope for very large text and fast concatenation.
History compaction
Merge repeated typing, cap history by memory budget, and checkpoint snapshots so undo is responsive without unbounded memory growth.
Crash recovery
Persist dirty buffers and recent commands or deltas periodically. Recovery should restore text, cursor, selection, and enough history for user trust.
Threading model
Keep document mutations on a single editor thread or guard them with a lock. Notify observers carefully so slow subscribers do not block typing.
International text
Java char counts UTF-16 code units, not user-visible characters. Production editors need grapheme-aware cursor movement and deletion.
Observability
Measure edit latency, undo depth, autosave failures, and memory usage. These signals catch history leaks and slow document operations early.
What Interviewers Look For
Does the candidate clearly separate Document, Command, and TextEditor responsibilities?
Do undo and redo stack transitions handle execute, undo, redo, and new edit after undo correctly?
Does the memento restore cursor and formatting as well as text?
Are clipboard actions mapped to commands where they mutate the document?
Can the design evolve to a better buffer structure without rewriting the command layer?
Are pattern names used for real design seams rather than decorative labels?
Quiz
0/5 answered
1.Why does **TextEditor** clear the redo stack after a new edit?
2.Which responsibility belongs in **Document**?
3.What does the Memento pattern protect in this design?
4.Why is **CharacterStyle** a good flyweight candidate?
5.Which action should normally be recorded in undo history?
Practice Variants
Grouped typing undo
IntermediateCoalesce repeated character inserts into one undoable command when they happen close together and at adjacent cursor positions.
Apply formatting command
AdvancedAdd a command that applies bold or italic to a selected range using CharacterStyle flyweights and restores prior style spans on undo.
Piece table implementation
AdvancedReplace StringBuilder internals with a piece table while preserving Document, Command, and TextEditor public APIs.
Flashcards
Cheat Sheet
Core objects: Document owns buffer + cursor; TextEditor owns undo/redo + clipboard; Command represents reversible edits; CharacterStyle shares formatting; EditorListener observes changes.
Undo flow: execute command → command saves memento → command mutates document → editor pushes undo → editor clears redo.
Redo flow: pop redo → execute command again → push undo → notify observers.
Clipboard: copy = store range only; cut = store range + delete command; paste = insert command with clipboard text.
Patterns: Command for edit actions; Memento for snapshots; Flyweight for styles; Observer for UI/autosave notifications.
Key invariant: only TextEditor mutates history stacks, and only Document mutates text/cursor internals.
Production upgrade: replace StringBuilder with gap buffer, piece table, or rope without changing command APIs.
References
- BookDesign Patterns: Elements of Reusable Object-Oriented Software — Gamma, Helm, Johnson, and Vlissides
- DocsRefactoring Guru — Command Pattern
- DocsRefactoring Guru — Memento Pattern
- BookThe Craft of Text Editing — Craig A. Finseth