Chaturmind
LearnDSASystem DesignInterview PrepDevOpsEngineering GrowthBlog
Start learning
Chaturmind

Structured learning paths for engineers who want to go deep. Written by practitioners.

Learn

  • Java
  • DSA
  • System Design
  • Spring Boot
  • AI / ML
  • DevOps
  • Engineering Growth
  • Java Interview Prep

Company

  • About
  • Blog
  • Contact

Legal

  • Privacy Policy
  • Terms of Service

© 2026 Chaturmind. All rights reserved.

👥10,050visitors

Built for engineers who want to go deep.


← Low-Level Design & Design Patterns

OOP Fundamentals & SOLID Principles

  • Object-Oriented Design Refresher
  • Single Responsibility & Open/Closed
  • Liskov Substitution & Interface Segregation
  • Dependency Inversion

Creational Design Patterns

  • Singleton Pattern
  • Factory & Abstract Factory
  • Builder Pattern
  • Prototype Pattern

Structural Design Patterns

  • Adapter & Facade
  • Decorator Pattern
  • Composite & Proxy
  • Bridge & Flyweight

Behavioral Design Patterns

  • Strategy Pattern
  • Observer Pattern
  • Command Pattern
  • Template Method
  • State Pattern
  • Chain of Responsibility
  • Iterator & Mediator
  • Visitor Pattern

Machine Coding Exercises

  • Parking Lot — Requirements & Class Design
  • Parking Lot — Implementation
  • Elevator System — Requirements & Class Design
  • Elevator System — Implementation & Scheduling
  • Library Management System
  • Tic-Tac-Toe / Board Game Design
  • Chess Engine Design
  • Vending Machine Design
  • ATM System Design
  • In-Memory Rate Limiter
  • Thread-Safe LRU Cache
  • Connection Pool Design
  • Producer-Consumer Class Design
  • Movie Ticket Booking System
  • Hotel Reservation System
  • Airline Booking / Seat Selection System
  • Splitwise / Expense Sharing System
  • Design a Logging Framework
  • Design a Notification/Observer-Based Pub-Sub
  • Design a Cache with Pluggable Eviction Policy
  • Design Snake and Ladder
  • Design a Card Game Framework
  • Design an Inventory Management System
  • Restaurant Management System
  • Food Delivery Order Matching
  • Design a Shopping Cart & Checkout Flow
  • Design a Recommendation Engine
  • Design a Job Scheduler
  • Design a Circuit Breaker
  • Design a Distributed ID Generator
  • Design a Pub-Sub Message Broker
  • Design Twitter/X
  • Multi-Pattern Problem: Design a Ride Booking Class Model
  • Multi-Pattern Problem: Design a Food Ordering Class Model
  • Design an Authentication/Authorization Module
  • Design a Form/Survey Builder
  • Design a Meeting Room / Calendar Booking System
  • Design a Search/Filter Engine for an E-Commerce Catalog
  • Design a Voting/Polling System
  • Design a Retry Mechanism with Backoff
  • Design a Health Check Aggregator
  • Design a Feature Flag System
  • Design a Config Management Client
  • Design a Leader Election Algorithm
  • Design a Distributed Counter
  • Design a Bloom Filter
  • Design a Consistent Hashing Ring
  • Design a Distributed Tracing Library
  • Design a Metrics Collection Library
  • Design an Audit Logging Framework
  • Design a Plugin/Extension System
  • Design a Workflow Engine
  • Multi-Pattern Capstone: Design a Food Delivery App
Chaturmind
← Low-Level Design & Design Patterns

OOP Fundamentals & SOLID Principles

  • Object-Oriented Design Refresher
  • Single Responsibility & Open/Closed
  • Liskov Substitution & Interface Segregation
  • Dependency Inversion

Creational Design Patterns

  • Singleton Pattern
  • Factory & Abstract Factory
  • Builder Pattern
  • Prototype Pattern

Structural Design Patterns

  • Adapter & Facade
  • Decorator Pattern
  • Composite & Proxy
  • Bridge & Flyweight

Behavioral Design Patterns

  • Strategy Pattern
  • Observer Pattern
  • Command Pattern
  • Template Method
  • State Pattern
  • Chain of Responsibility
  • Iterator & Mediator
  • Visitor Pattern

Machine Coding Exercises

  • Parking Lot — Requirements & Class Design
  • Parking Lot — Implementation
  • Elevator System — Requirements & Class Design
  • Elevator System — Implementation & Scheduling
  • Library Management System
  • Tic-Tac-Toe / Board Game Design
  • Chess Engine Design
  • Vending Machine Design
  • ATM System Design
  • In-Memory Rate Limiter
  • Thread-Safe LRU Cache
  • Connection Pool Design
  • Producer-Consumer Class Design
  • Movie Ticket Booking System
  • Hotel Reservation System
  • Airline Booking / Seat Selection System
  • Splitwise / Expense Sharing System
  • Design a Logging Framework
  • Design a Notification/Observer-Based Pub-Sub
  • Design a Cache with Pluggable Eviction Policy
  • Design Snake and Ladder
  • Design a Card Game Framework
  • Design an Inventory Management System
  • Restaurant Management System
  • Food Delivery Order Matching
  • Design a Shopping Cart & Checkout Flow
  • Design a Recommendation Engine
  • Design a Job Scheduler
  • Design a Circuit Breaker
  • Design a Distributed ID Generator
  • Design a Pub-Sub Message Broker
  • Design Twitter/X
  • Multi-Pattern Problem: Design a Ride Booking Class Model
  • Multi-Pattern Problem: Design a Food Ordering Class Model
  • Design an Authentication/Authorization Module
  • Design a Form/Survey Builder
  • Design a Meeting Room / Calendar Booking System
  • Design a Search/Filter Engine for an E-Commerce Catalog
  • Design a Voting/Polling System
  • Design a Retry Mechanism with Backoff
  • Design a Health Check Aggregator
  • Design a Feature Flag System
  • Design a Config Management Client
  • Design a Leader Election Algorithm
  • Design a Distributed Counter
  • Design a Bloom Filter
  • Design a Consistent Hashing Ring
  • Design a Distributed Tracing Library
  • Design a Metrics Collection Library
  • Design an Audit Logging Framework
  • Design a Plugin/Extension System
  • Design a Workflow Engine
  • Multi-Pattern Capstone: Design a Food Delivery App
HomeLearnSystem DesignLow-Level Design & Design PatternsBehavioral Design Patterns
✓ FreeIntermediate· 8 min read

Command Pattern

Encapsulating a request as an object to enable undo/redo and queuing — built through a text editor's undo history, and why Command is more than Strategy with a fancier name.

Published September 23, 2026


Command Pattern

The idea: a request as an object, not a method call

A plain method call happens and is gone — there's nothing left to inspect, queue, delay, or undo afterward. Command wraps a request (an action plus the data it needs) into an object, so the request itself becomes a first-class thing you can store, pass around, and reverse.

interface Command {
    void execute();
    void undo();
}

class TextDocument {
    private final StringBuilder content = new StringBuilder();
    void insert(int pos, String text) { content.insert(pos, text); }
    void delete(int pos, int length) { content.delete(pos, pos + length); }
    String getText() { return content.toString(); }
}

class InsertCommand implements Command {
    private final TextDocument doc;
    private final int position;
    private final String text;

    InsertCommand(TextDocument doc, int position, String text) {
        this.doc = doc; this.position = position; this.text = text;
    }
    public void execute() { doc.insert(position, text); }
    public void undo() { doc.delete(position, text.length()); } // the exact inverse of execute()
}

Advertisement

Undo/redo via a command history stack

class EditorHistory {
    private final Deque<Command> undoStack = new ArrayDeque<>();
    private final Deque<Command> redoStack = new ArrayDeque<>();

    void executeCommand(Command cmd) {
        cmd.execute();
        undoStack.push(cmd);
        redoStack.clear(); // a fresh action invalidates any previously-undone redo history
    }

    void undo() {
        if (undoStack.isEmpty()) return;
        Command cmd = undoStack.pop();
        cmd.undo();
        redoStack.push(cmd);
    }

    void redo() {
        if (redoStack.isEmpty()) return;
        Command cmd = redoStack.pop();
        cmd.execute();
        undoStack.push(cmd);
    }
}

Every executed command is pushed onto undoStack after running. Undo pops the most recent one and calls its undo() — moving it to redoStack so redo can re-execute it later. The redoStack.clear() on a fresh action is the detail easy to miss: once you undo twice and then type something new, the two undone actions are no longer a valid "redo" path — they'd redo into a document state that no longer makes sense given the new edit.

Command vs Strategy — both wrap behavior, different purpose

Strategy (see Strategy Pattern) wraps an interchangeable algorithm — the point is picking which implementation runs, with no concept of "undo" or "when." Command wraps a request, specifically so it can be queued, delayed, logged, or reversed — the "when" (execute now? later? as part of a batch?) and the "undo" are exactly what Command adds that Strategy doesn't model at all. A PricingStrategy has no meaningful "undo"; an InsertCommand does, by design.

A complete undo/redo history, with macros and merging

The basic history stack supports undo. A usable editor also needs redo, clears the redo stack when a new command is executed, groups several commands into one undoable step, and merges rapid keystrokes, so undo doesn't remove one character at a time:

interface Command {
    void execute();
    void undo();
    default boolean mergeWith(Command next) { return false; }   // optional coalescing
}

final class History {
    private final Deque<Command> undoStack = new ArrayDeque<>();
    private final Deque<Command> redoStack = new ArrayDeque<>();
    private final int limit;

    History(int limit) { this.limit = limit; }

    void run(Command c) {
        c.execute();
        redoStack.clear();                                   // a new action invalidates the redo branch
        Command last = undoStack.peek();
        if (last != null && last.mergeWith(c)) return;       // typed "h","e","l","l","o" → one undo step
        undoStack.push(c);
        if (undoStack.size() > limit) undoStack.removeLast(); // bound the memory used by history
    }

    void undo() {
        Command c = undoStack.poll();
        if (c == null) return;
        c.undo();
        redoStack.push(c);
    }

    void redo() {
        Command c = redoStack.poll();
        if (c == null) return;
        c.execute();
        undoStack.push(c);
    }
}

Macro commands (Composite) make a multi-step operation undoable as one unit:

final class MacroCommand implements Command {
    private final List<Command> steps;
    MacroCommand(List<Command> steps) { this.steps = List.copyOf(steps); }

    public void execute() { steps.forEach(Command::execute); }
    public void undo() {
        for (int i = steps.size() - 1; i >= 0; i--) steps.get(i).undo();   // reverse order
    }
}

history.run(new MacroCommand(List.of(new ReplaceCommand(doc, "colour", "color"),
                                     new ReplaceCommand(doc, "favour", "favor"))));   // "Replace all" = one undo

Merging lets consecutive typing at adjacent positions coalesce:

final class InsertCommand implements Command {
    private final TextDocument doc; private int position; private String text;
    InsertCommand(TextDocument doc, int position, String text) { this.doc = doc; this.position = position; this.text = text; }

    public void execute() { doc.insert(position, text); }
    public void undo() { doc.delete(position, text.length()); }

    @Override public boolean mergeWith(Command next) {
        if (next instanceof InsertCommand n && n.doc == doc && n.position == position + text.length()
                && !text.endsWith(" ")) {                    // stop merging at word boundaries
            text = text + n.text;                            // n has already executed; just absorb it
            return true;
        }
        return false;
    }
}

Design notes:

  • Redo is cleared on a new action. After undoing twice and then typing, the undone commands belong to an abandoned branch, and redoing them would corrupt the document.
  • A history limit bounds memory. Commands that store large snapshots should be counted by size, not number.
  • Reverse-order undo in macros mirrors how the changes were applied.

Beyond undo: commands as data

Because a command is an object, it can be stored, queued, sent and replayed:

  • Job queues: a SendInvoiceEmail(invoiceId) command serialized onto a queue is executed later by a worker. That's the Command pattern across process boundaries.
  • Audit logs and replay: recording every command applied to a system lets you reconstruct its state, which is the idea behind event sourcing, where the recorded items are events describing what happened.
  • Transactions and batching: collect commands, validate them all, then execute them together, or reject the batch.
  • Remote control and UI actions: menu items, buttons and keyboard shortcuts all trigger the same SaveCommand, so behaviour is defined once.

A useful distinction in backend design: a command expresses intent ("PlaceOrder"), and may be rejected. An event records a fact ("OrderPlaced") that has already happened. Keeping the two separate makes systems easier to reason about.

Follow-up questions this topic invites — and their answers

Q: How would you implement a 'macro' — a single command that bundles several commands together? A: A CompositeCommand implementing the same Command interface, holding a List<Command>, whose execute() runs each child in order and whose undo() undoes them in reverse order — this is Command combined with Composite (see Composite & Proxy), and the reverse-order undo is the detail that makes it correct: undoing must unwind history in the opposite order it was built.

Q: Why does InsertCommand.undo() call doc.delete() instead of storing a full document snapshot? A: Storing the minimal inverse operation (delete the exact range that was inserted) is far cheaper than snapshotting the whole document before every command — this matters a lot for a text editor where documents can be large and edits are frequent; snapshot-based undo only makes sense when computing a precise inverse operation is itself impractical.

Q: Can Command be used for something other than undo/redo? A: Yes — queuing (store commands in a queue and execute them later, e.g. a job queue), logging/auditing (every command object is a natural audit-log entry of what happened and when), and remote execution (serialize a command object and execute it on a different machine) are all common Command applications that have nothing to do with undo.

Q: What happens to undo() correctness if InsertCommand's undo() assumes nothing else modified the document between execute() and undo()? A: It breaks — if another command inserted or deleted text at an overlapping position in between, the stored position/length in InsertCommand.undo() would delete the wrong range. This is why undo/redo systems generally require commands to be undone in strict LIFO order relative to how they were executed (which the stack-based EditorHistory above enforces by construction) rather than allowing arbitrary out-of-order undo.

Q: Why must the redo stack be cleared when a new command runs? A: Redo re-applies commands that were undone from a specific document state. Once a new command changes the document, that state is gone, and re-applying the old commands would act on text positions and content that no longer match.

Q: How do macro commands keep undo correct? A: They execute their child commands in order, and undo them in reverse order, so each child's undo sees exactly the state its execute produced. The history treats the macro as one entry, so a single undo reverts the whole operation.

Q: What's the difference between a command and an event in backend systems? A: A command is a request to do something, and may be validated and rejected ("ReserveSeat"). An event states that something already happened, and can't be rejected, only reacted to ("SeatReserved"). Commands usually go to one handler, while events may have many subscribers.

Advertisement

Previous

Observer Pattern

Next

Template Method

AI Tutor

Lesson: Command Pattern

Quick actions

AI responses can be inaccurate. Verify critical information.