Compile Ready
All low level design problems
Low Level Design/Advanced/Advanced Systems

Design a Text Editor

Insert/delete, cursor movement, and undo/redo via Command + Memento, with a gap-buffer document model.

Advanced 55m interview 15m read Medium frequency Popularity 79
Command Memento Strategy Google Microsoft Adobe

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

Rendering diagram…
The editor shell owns orchestration: execute command, push undo stack, clear redo stack, and notify observers. The document owns the buffer, cursor, and memento snapshots.

Sequence Diagram

Rendering diagram…
Undo is command-driven but state restoration is document-driven. The command asks the document for a memento before executing, then restores that memento when undone.

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.

buffercursorstyleSpans

TextEditor

Application facade over the editing core. Converts user actions into commands, manages undo/redo stacks, stores clipboard text, and notifies observers after changes.

documentundoStackredoStackclipboardlisteners

Command

Common interface for reversible mutations. Every edit supports execute, undo, and a human-readable name for history displays.

executeundoname

InsertCommand

Encapsulates inserting text at the current cursor with a style. It saves a document memento before the mutation and restores it on undo.

documenttextstylebefore

DeleteCommand

Encapsulates deleting text from the current cursor. It captures the pre-delete memento and remembers the removed text for diagnostics or command history.

documentlengthremovedTextbefore

CharacterStyle

Immutable flyweight for repeated formatting attributes. Identical font, size, bold, and italic combinations are shared through a cache.

fontsizebolditaliccache

EditorListener

Observer interface for UI, autosave, status bar, or telemetry subscribers that need to react whenever editor-visible state changes.

onChange(action, text, cursor)

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.

clipboard

Design Patterns Used

Command

Every mutating operation is a Command object with execute and undo. The editor history stores command objects, not ad hoc lambdas or switch cases.

Memento

Document.save returns an opaque snapshot of text, cursor, and styles. Commands keep the snapshot but cannot mutate its internals, preserving document encapsulation.

Flyweight

CharacterStyle.of shares immutable formatting objects for repeated style combinations, so thousands of characters can reference the same style instance.

Observer

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

  1. 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;
    }
  2. 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();
    }
  3. 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);
    }
  4. 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.

  5. 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.

  6. 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));
    }
  7. 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

Loading…

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.

StepActionBufferCursorUndo stackRedo stack
1type HelloHello5[Insert Hello][]
2type space plus worldHello world11[Insert world, Insert Hello][]
3move cursor to 6Hello world6[Insert world, Insert Hello][]
4delete 5Hello 6[Delete world, Insert world, Insert Hello][]
5undoHello world6[Insert world, Insert Hello][Delete world]
6copy world and paste at endHello worldworld16[Paste world, Insert world, Insert Hello][]
7undo then redo pasteHello worldworld16[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

OperationTimeSpaceNote
insertO(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.
deleteO(n + s)O(n)Deletion shifts remaining text and stores the pre-delete memento for undo.
copyO(k)O(k)Copies k characters into the clipboard and does not affect undo history.
cutO(n + k + s)O(n + k)Copies k characters to the clipboard and executes a delete command with a memento.
pasteO(n + k + s)O(n)Executes an insert command for k clipboard characters and clears redo.
undo / redoO(n + s)O(1) extraRestores 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. 1.Why does **TextEditor** clear the redo stack after a new edit?

  2. 2.Which responsibility belongs in **Document**?

  3. 3.What does the Memento pattern protect in this design?

  4. 4.Why is **CharacterStyle** a good flyweight candidate?

  5. 5.Which action should normally be recorded in undo history?

Practice Variants

Grouped typing undo

Intermediate

Coalesce repeated character inserts into one undoable command when they happen close together and at adjacent cursor positions.

Apply formatting command

Advanced

Add a command that applies bold or italic to a selected range using CharacterStyle flyweights and restores prior style spans on undo.

Piece table implementation

Advanced

Replace 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