Design Patterns Tutorial 0/70 lessons ~6 min read Lesson 34

    Command Pattern

    Command turns a method call into a first-class object you can store, queue, schedule, undo, log, or replay.

    Course progress0%
    Focus
    21 guided sections
    Practice signal
    Examples included
    Career prep
    Interview Q&A included

    Introduction

    Command turns a method call into a first-class object you can store, queue, schedule, undo, log, or replay. The invoker triggers commands without knowing what they do; the command knows receiver and operation. Shows up in task queues, CQRS, undo stacks, UI actions, and Kubernetes desired-state declarations.

    The story

    A document editor needed undo across 40 operations — each had been a direct method call, un-undoable. Command per operation with execute() and undo() made undo/redo a stack push/pop and enabled macros and crash recovery.

    The business problem

    Direct method calls can't be queued, undone, or audited:

    • No undo: operations mutate state with no reversal path.
    • No audit trail: compliance can't replay what admin did.
    • Invoker coupling: button/scheduler knows every operation type.
    • No async queue: long work blocks caller — no retry/replay.

    The problem teams faced

    Command fits when:

    • You need undo/redo/replay.
    • Requests must be queued, logged, or retried.
    • Same invoker triggers many different operations.
    • Operations cross transaction boundaries (outbox, WAL).

    Understanding the topic

    Intent: Encapsulate a request as an object.

    • Command — execute() and often undo().
    • ConcreteCommand — binds receiver + parameters.
    • Invoker — triggers commands (button, queue, scheduler).
    • Receiver — performs actual work.

    Internal architecture

    Undoable command:

    ts
    interface Command { execute(): void; undo(): void }
    class InsertTextCommand implements Command {
    private prevText: string
    constructor(private doc: Document, private text: string, private pos: number) {}
    execute() {
    this.prevText = this.doc.getText(this.pos, this.text.length)
    this.doc.insert(this.pos, this.text)
    }
    undo() { this.doc.replace(this.pos, this.text.length, this.prevText) }
    }
    class Editor {
    private undoStack: Command[] = []
    run(cmd: Command) { cmd.execute(); this.undoStack.push(cmd) }
    undo() { const cmd = this.undoStack.pop(); cmd?.undo() }
    }

    Visual explanation

    Three diagrams cover command flow, undo stack, and queue:

    Command execution
    Client builds
    ConcreteCommand
    Invoker.trigger()
    Knows nothing
    command.execute()
    Calls receiver
    Push undo stack
    Optional
    Invoker decoupled from operation — command carries intent.
    Undo / redo stack
    execute()
    Push to undo stack
    undo()
    Pop · reverse
    redo()
    Push to redo stack
    Memento inside
    State snapshot
    Command + Memento powers undo — each command knows how to reverse.
    Command queue
    API accepts
    PlaceOrderCommand
    Persist queue
    BullMQ · outbox
    Worker executes
    Retry on fail
    Audit log
    Compliance
    Command as message — async, durable, replayable.

    Informative example

    Before / after — direct calls to Command:

    ts
    // ❌ Direct mutation — no undo, no audit
    function onDeleteRow(table: Table, rowId: string) {
    table.deleteRow(rowId)
    }
    // ✅ Command — undoable + auditable
    class DeleteRowCommand implements Command {
    private deletedRow?: Row
    constructor(private table: Table, private rowId: string) {}
    execute() {
    this.deletedRow = this.table.getRow(this.rowId)
    this.table.deleteRow(this.rowId)
    }
    undo() { if (this.deletedRow) this.table.insertRow(this.deletedRow) }
    }
    // Invoker
    editor.run(new DeleteRowCommand(table, rowId))
    auditLog.persist(command) // serialize intent before execute

    Execution workflow

    1Command lifecycle
    1 / 4

    Client builds

    ConcreteCommand bound to receiver + args.

    Factory creates from UI action or API payload.

    Real-world use

    Java Runnable/Callable, Swing Action, Sidekiq/Celery/BullMQ jobs, CQRS command buses, Git operations, Kubernetes controllers, transactional outbox messages.

    Production case study

    Admin audit + undo:

    • Scenario: SaaS needed audit trail of every admin action with rollback.
    • Decision: Each admin op as Command persisted before execute.
    • Outcome: Full compliance audit; ops got "undo last admin action."

    Trade-offs

    • Pro: undo, queue, audit, replay from one abstraction.
    • Pro: invoker decoupled from operations.
    • Con: boilerplate per operation class.
    • Con: undo complexity for non-reversible side effects (email sent).

    Decision framework

    • Use when undo/redo required.
    • Use when operations queued async with retry.
    • Use when audit trail needs intent serialization.
    • Avoid for simple synchronous CRUD with no undo/audit need.

    Best practices

    • Commands carry intent + data — not domain objects with behaviour.
    • Compensating commands for irreversible async effects (Saga).
    • Idempotent execute when replayed from queue.

    Anti-patterns to avoid

    • Command that is just a function wrapper with no undo/queue/audit benefit.
    • God-command doing unrelated operations — split by intent.
    • Undo that doesn't capture sufficient state — corrupts document.

    Common mistakes

    • Undo after irreversible side effect (email) — need compensating action.
    • Command serialization versioning when schema changes.
    • Huge command payloads — store reference not full aggregate.

    Debugging tips

    • Log command type + payload at execute and undo.
    • Replay command log in test to reproduce production state.

    Optimization strategies

    • Batch small commands into macro command for undo granularity control.
    • Compress command log for long-running sessions.

    Common misconceptions

    • Command (intent) ≠ Domain Event (fact). Command: do X. Event: X happened.
    • CQRS command bus is Command pattern at architecture scale.
    • Runnable is Command — minimal form.

    Advanced interview questions

    Interview Prep

    Practice concise answers, then expand each card for the explanation.

    4 questions
    1IntermediateQuestionCommand vs Strategy?+

    Answer

    Command encapsulates a request/action with optional undo. Strategy encapsulates interchangeable algorithm — no request object lifecycle.

    Follow-up

    PlaceOrderCommand vs PricingStrategy?
    2AdvancedQuestionCommand vs Domain Event?+

    Answer

    Command: intent inward ('place order'). Event: fact outward ('order placed'). CQRS uses both.

    Follow-up

    Can same object be both?
    3AdvancedQuestionUndo for sent email?+

    Answer

    Can't undo — use compensating command (SendCorrectionEmail) or Saga. Command undo works for reversible local state.

    Follow-up

    Saga pattern?
    4BeginnerQuestionWhere Command in UI?+

    Answer

    Every button action bound to Command — enables macro recording, undo, disabled state centralization.

    Follow-up

    Invoker role?

    Summary

    You can model undo stacks, job queues, and audit trails with Command. Tell the 40-operation document editor story — if you can do that, you own Command.

    Ready to mark this lesson complete?Track your journey across the entire course.