Skip to main content
CleanCodeMastery

Command Pattern: Turn Every Action into an Order Slip

Learn the Command pattern through a restaurant order slip story. Simple TypeScript and C# code with undo and redo, diagrams, tables, and practice tasks.

23 min read Updated June 11, 2026beginner
design-patternsbehavioral-patternscommandundo-redotypescriptcsharpoop

🍛 One plate of paneer butter masala, please

Meet Aarav. It is a busy Sunday evening, and Aarav has brought his family to Hotel Annapurna, a famous little restaurant in Mumbai. They squeeze into table 4. The waiter, Ravi, comes over with a smile and a small notepad. Aarav orders: "One paneer butter masala, two butter naans, and one sweet lassi."

Does Ravi run to the stove and start cooking? No! He writes the order on a slip. He tears off the slip and clips it onto a steel rail at the kitchen window. Inside, Chef Lakshmi picks up slips one by one and cooks whatever each slip says. She has never seen Aarav. Aarav has never seen her. The slip connects them.

Stop and look carefully at that little paper slip. It is doing something quietly magical:

  • Aarav (the customer) never talks to the chef. He does not know who is cooking tonight.
  • Ravi carries the slip but cannot cook. He does not even need to understand recipes.
  • The slip holds everything needed: which dish, how many, which table. It can wait in a queue on the rail. If a plate falls, Chef Lakshmi can cook it again from the same slip. At closing time, the owner, Mr. D'Souza, counts all the slips to know the day's business.
  • Chef Lakshmi reads the slip and does the real work.

And one more thing — halfway through the evening, Aarav's little sister changes her mind: "Bhaiya, cancel the lassi!" Ravi simply pulls that slip off the rail. The order is undone before it ever reaches the stove. Later she changes her mind again, and Ravi clips the same slip back. The order is redone. No drama, no rewriting — the slip was already there.

That order slip is the Command pattern. A request, written down as an object, that can travel, wait, repeat, and even be cancelled. Hold on to this restaurant story — every section below follows Aarav's table 4.

Here is the evening at a glance:

Figure 1: The order slip's path from table 4 to the kitchen

And here is the same evening told as a journey:

Figure 2: One order slip's trip through Hotel Annapurna

🎯 What is the Command pattern?

Command is a behavioral design pattern from the Gang of Four (GoF) book (1994), where it is also called Action or Transaction.

Here is a definition you can keep for revision:

The Command pattern turns a request into a standalone object. The object stores everything about the request — the receiver, the operation, and its parameters — behind one tiny interface, usually a single execute() method (and often an undo() method too).

Once a request becomes an object, you can do things that are impossible with a plain method call:

  • Store it in a variable or a list, like slips on the rail.
  • Queue it to run later, or on another thread, like slips waiting their turn.
  • Log it to disk, so a crashed program can replay its history, like Mr. D'Souza counting slips at night.
  • Undo it, because the object can remember how to reverse itself, like pulling a slip off the rail.

The pattern has five roles. Match them with the restaurant:

RoleJobRestaurant version
CommandInterface with execute() (and undo())The idea of "an order slip"
ConcreteCommandOne real request; stores receiver + detailsA filled slip: "1 paneer, table 4"
InvokerTriggers commands; never cooksRavi and the kitchen rail
ReceiverDoes the actual workChef Lakshmi
ClientCreates commands and wires everythingAarav, the customer ordering
💡

Memory trick: "Write it, don't shout it." Instead of shouting your order at the chef (a direct method call), you write a slip (a command object). Anything written down can wait, be repeated, be counted, and be torn up. That is the whole power of this pattern in one line.

The whole pattern fits on one mind map. Redraw it from memory before your exam:

Figure 3: The Command pattern at a glance

😖 The problem it solves

Let us see the pain without the pattern. Suppose Mr. D'Souza hires you to build the restaurant's billing screen. It has buttons: Add Item, Remove Item, Apply Discount. The lazy way is to put the work directly inside each button:

// ❌ Each button directly does the work — tight coupling everywhere
addItemButton.onClick = () => {
  bill.items.push({ name: "Paneer Butter Masala", price: 320 });
  screen.refresh();
};
 
removeItemButton.onClick = () => {
  bill.items.pop();
  screen.refresh();
};

Looks harmless. Now the problems arrive one by one, just like Sunday customers:

  1. The keyboard shortcut problem. Mr. D'Souza wants Ctrl+Z to undo the last action. But the actions were never recorded anywhere! The work happened and vanished — like a shouted order that nobody wrote down. There is nothing to undo.
  2. The duplicate logic problem. The same "add item" must now also work from a menu entry and from a touchscreen. You copy-paste the logic into three places. One day you fix a bug in one place and forget the other two.
  3. The queue problem. During rush hour, orders should go into a queue and run one by one. But a button click is not a thing you can put into a queue. It just happens instantly.
  4. The history problem. Mr. D'Souza asks, "Show me every action taken today." You have no record, because actions were never objects — they were just method calls that disappeared.

The deep issue is coupling: the thing that triggers work (button) is glued to the thing that does work (bill). There is no layer in between where a request can be held, stored, or reversed. The restaurant without slips is a restaurant where customers shout at the chef — chaos at 8 pm, and no record at 11 pm.

The Command pattern inserts the missing layer: every trigger creates or holds a command object, and only the command talks to the receiver. The invoker also pushes each executed command onto a history stack — and suddenly undo, logging, and queues become easy.

⚙️ How it works, step by step

Let us assemble the pattern piece by piece, using the restaurant.

Step 1: Declare the Command interface. Two methods: execute() and undo().

Step 2: Identify the receiver. The class that already knows the real work — our Kitchen (or in an editor, the Document). Do not move business logic into commands. Chef Lakshmi cooks; the slip never cooks. Commands only delegate.

Step 3: Write one concrete command per request type. PrepareDishCommand, CancelDishCommand. Each stores its receiver and its parameters in the constructor, so it is fully ready to run — a filled slip, not a blank one.

Step 4: Build the invoker. Our Waiter holds commands, runs them, and keeps a history stack. After execute(), push the command onto the stack. For undo, pop and call undo().

Step 5: Wire it in the client. Create the kitchen, create commands around it, hand them to the waiter.

Watch one order travel through the system, message by message:

Figure 4: One order slip travelling from customer to kitchen, then being undone

Ravi's code is beautifully ignorant. He runs execute() on whatever slip he holds — paneer, biryani, or dessert — all look identical to him. That uniformity is exactly why the same invoker can also power queues and undo stacks.

The undo machinery follows four small rules. Learn them — interviewers love them:

execute a command        -> push it onto the UNDO stack
undo                     -> pop UNDO, call undo(), push onto REDO stack
redo                     -> pop REDO, call execute(), push onto UNDO stack
new command after undo   -> CLEAR the redo stack

Why clear the redo stack? Imagine Aarav undoes "add lassi", then orders "add gulab jamun". Redoing "add lassi" now would replay a future that no longer makes sense — the evening has moved on. Clearing keeps history honest.

A single command also lives a small life of its own. Here it is as states:

Figure 5: The life of one command, from creation to undo and redo

College corner: the undo stack you are about to build is the exact machinery inside real editors, but production systems add limits and tricks. Editors cap the stack depth (say, the last 200 commands) so memory stays bounded. They coalesce tiny commands — typing "hello" becomes one command, not five, so one Ctrl+Z removes the word, not one letter. And they distinguish two undo styles: inverse-operation undo (the command knows its reverse, cheap on memory) versus snapshot undo (store the old state before executing — the Memento pattern — heavier but works when an action has no clean mathematical reverse, like "apply blur filter"). Most large apps mix both.

💻 Real-life code example

Here is the full restaurant in TypeScript — with working undo and redo. Read the comments; they carry the story.

// ----- The RECEIVER: the kitchen does the real work -----
class Kitchen {
  private board: string[] = []; // dishes currently being prepared
 
  prepare(dish: string): void {
    this.board.push(dish);
    console.log(`Kitchen: started preparing ${dish}.`);
  }
 
  stopPreparing(dish: string): void {
    const index = this.board.lastIndexOf(dish);
    if (index !== -1) {
      this.board.splice(index, 1);
      console.log(`Kitchen: cancelled ${dish}.`);
    }
  }
 
  showBoard(): void {
    console.log(`Kitchen board: [${this.board.join(", ")}]`);
  }
}
 
// ----- The COMMAND interface: every order slip looks the same -----
interface Command {
  execute(): void;
  undo(): void;
}
 
// ----- A CONCRETE COMMAND: one filled order slip -----
class PrepareDishCommand implements Command {
  // The slip stores the receiver AND the parameters.
  constructor(
    private kitchen: Kitchen,
    private dish: string,
  ) {}
 
  execute(): void {
    this.kitchen.prepare(this.dish); // delegate to the receiver
  }
 
  undo(): void {
    this.kitchen.stopPreparing(this.dish); // the reverse action
  }
}
 
// ----- The INVOKER: the waiter runs slips and keeps history -----
class Waiter {
  private undoStack: Command[] = [];
  private redoStack: Command[] = [];
 
  place(command: Command): void {
    command.execute();
    this.undoStack.push(command);
    this.redoStack = []; // a fresh action clears the redo future
  }
 
  undo(): void {
    const command = this.undoStack.pop();
    if (!command) {
      console.log("Waiter: nothing to undo.");
      return;
    }
    command.undo();
    this.redoStack.push(command);
  }
 
  redo(): void {
    const command = this.redoStack.pop();
    if (!command) {
      console.log("Waiter: nothing to redo.");
      return;
    }
    command.execute();
    this.undoStack.push(command);
  }
}
 
// ----- The CLIENT: Aarav orders -----
const kitchen = new Kitchen();
const waiter = new Waiter();
 
waiter.place(new PrepareDishCommand(kitchen, "Paneer Butter Masala"));
waiter.place(new PrepareDishCommand(kitchen, "Butter Naan"));
waiter.place(new PrepareDishCommand(kitchen, "Sweet Lassi"));
kitchen.showBoard();
 
console.log("\nAarav's sister: cancel the lassi please!");
waiter.undo(); // undo last order
kitchen.showBoard();
 
console.log("\nAarav's sister: actually, bring the lassi back!");
waiter.redo(); // redo it
kitchen.showBoard();

Output:

Kitchen: started preparing Paneer Butter Masala.
Kitchen: started preparing Butter Naan.
Kitchen: started preparing Sweet Lassi.
Kitchen board: [Paneer Butter Masala, Butter Naan, Sweet Lassi]
 
Aarav's sister: cancel the lassi please!
Kitchen: cancelled Sweet Lassi.
Kitchen board: [Paneer Butter Masala, Butter Naan]
 
Aarav's sister: actually, bring the lassi back!
Kitchen: started preparing Sweet Lassi.
Kitchen board: [Paneer Butter Masala, Butter Naan, Sweet Lassi]

Notice three lovely things:

  1. The waiter class never mentions food. It would work unchanged for a drawing app (DrawCircleCommand) or a smart home (LightsOnCommand). Invokers are fully reusable.
  2. Undo cost us almost nothing. Each command knows its own reverse, and the waiter keeps two stacks. That is the entire machinery behind Ctrl+Z in real editors.
  3. A macro is trivial. A "Family Combo" command can hold a list of commands and run them all — a command made of commands (Command + Composite):
class ComboCommand implements Command {
  constructor(private commands: Command[]) {}
 
  execute(): void {
    this.commands.forEach((c) => c.execute());
  }
 
  undo(): void {
    // undo in REVERSE order — last action reversed first
    [...this.commands].reverse().forEach((c) => c.undo());
  }
}
 
waiter.place(
  new ComboCommand([
    new PrepareDishCommand(kitchen, "Veg Biryani"),
    new PrepareDishCommand(kitchen, "Raita"),
  ]),
);
waiter.undo(); // cancels Raita first, then Biryani — one clean undo

Watch how the history stacks breathe as you work. Every edit grows the undo stack; every undo shrinks it (and grows redo):

Figure 6: The undo stack growing and shrinking during a session

Look at the last bar carefully: after the new edit, the count is 9, not 10. The two commands still sitting on the redo stack were cleared by the new action — rule four in our text box, working exactly as promised.

🟦 The same idea in C#

C# makes the pattern feel right at home — WPF and other UI frameworks even ship a built-in ICommand interface. Here is our restaurant, shortened:

public interface ICommandSlip
{
    void Execute();
    void Undo();
}
 
// Receiver
public class Kitchen
{
    private readonly List<string> _board = new();
 
    public void Prepare(string dish)
    {
        _board.Add(dish);
        Console.WriteLine($"Kitchen: preparing {dish}.");
    }
 
    public void StopPreparing(string dish)
    {
        _board.Remove(dish);
        Console.WriteLine($"Kitchen: cancelled {dish}.");
    }
}
 
// Concrete command
public class PrepareDishCommand : ICommandSlip
{
    private readonly Kitchen _kitchen;
    private readonly string _dish;
 
    public PrepareDishCommand(Kitchen kitchen, string dish)
        => (_kitchen, _dish) = (kitchen, dish);
 
    public void Execute() => _kitchen.Prepare(_dish);
    public void Undo() => _kitchen.StopPreparing(_dish);
}
 
// Invoker with history
public class Waiter
{
    private readonly Stack<ICommandSlip> _history = new();
 
    public void Place(ICommandSlip slip)
    {
        slip.Execute();
        _history.Push(slip);
    }
 
    public void UndoLast()
    {
        if (_history.TryPop(out var slip)) slip.Undo();
    }
}
 
// Client
var kitchen = new Kitchen();
var waiter = new Waiter();
waiter.Place(new PrepareDishCommand(kitchen, "Masala Dosa"));
waiter.Place(new PrepareDishCommand(kitchen, "Filter Coffee"));
waiter.UndoLast(); // cancels Filter Coffee

And the same skeleton in Python, for completeness — commands are just objects with two methods:

class PrepareDishCommand:
    def __init__(self, kitchen, dish):
        self.kitchen = kitchen
        self.dish = dish
 
    def execute(self):
        self.kitchen.prepare(self.dish)
 
    def undo(self):
        self.kitchen.stop_preparing(self.dish)
 
 
class Waiter:
    def __init__(self):
        self.history = []
 
    def place(self, command):
        command.execute()
        self.history.append(command)
 
    def undo_last(self):
        if self.history:
            self.history.pop().undo()

In modern C#, TypeScript, and Python, very simple commands can even be plain lambdas (() => kitchen.Prepare("Dosa")). That removes class ceremony — but the moment you need undo() or serialization, a proper command class earns its keep.

Here is the full class structure of what we built:

Figure 7: The five roles of the Command pattern and how they connect

🌍 Where you see it in real software

Once you know this pattern, you will spot order slips everywhere.

1. Undo/redo in editors. Microsoft Word, Google Docs, Photoshop, and VS Code all track your edits as command-like objects on history stacks. Press Ctrl+Z and the editor pops the last command and reverses it. Behind every undo arrow is a rail of slips.

2. Job and task queues. Background job systems (think email senders, report generators, message queues) serialize "what to do" into a stored object, push it onto a queue, and a worker executes it later — maybe on a different machine. A job is a command that learned to travel.

3. Redux actions. In Redux, every change to the app's state is a plain action object like a typed note saying "ADD_ITEM with this payload" — a request turned into data. Because actions are objects, tools like redux-undo can give whole apps undo/redo almost for free, and the Redux DevTools can log and replay every action (that replay is "time-travel debugging").

4. GUI buttons and menus. Classic desktop frameworks (Java Swing's Action, WPF's ICommand) let one command power a toolbar button, a menu item, and a keyboard shortcut at once — fixing the duplicate-logic problem we met earlier.

5. Transactions and recovery. Databases and some servers write each operation to a log before applying it. After a crash, they replay the logged commands to rebuild the latest consistent state. "Transaction" is literally one of this pattern's official alternate names.

6. Smart home routines. "Good night" on a voice assistant runs a macro command: lights off, doors locked, AC to 24 degrees. One trigger, many slips.

Real softwareThe command objectWhat the pattern enables
Word / Docs / VS CodeAn edit operationUndo/redo stacks
Background job queuesA serialized jobRun later, retry, run elsewhere
ReduxAn action objectLogging, time travel, redux-undo
WPF / Swing UIsICommand / ActionOne action, many triggers
DatabasesA logged transactionCrash recovery by replay
Smart home scenesA routine of device commandsMacros from one trigger

College corner: push the "log every command" idea to its limit and you get event sourcing. In an event-sourced system, the log of commands and the events they produce is the database. Your bank account is not stored as one number — it is the replay of every deposit and withdrawal since the account opened. Need the balance? Replay the slips. Need the balance as it was last March? Replay the slips up to last March. Mr. D'Souza already does this: his pile of order slips, replayed at midnight, is his accounts book. Frameworks like EventStoreDB and patterns like CQRS build entire architectures on this one insight: if every change is an object, history becomes data you can query, audit, and time-travel through.

At closing time, Mr. D'Souza sorts the day's slips. Most got cooked; a few were cancelled; a couple were cancelled and then brought back, like the famous lassi:

Figure 8: The fate of all order slips on one Sunday evening

🤔 When to use it and when not to

SituationUse it?Why
You need undo/redoThe classic, number-one reason
Operations must be queued, scheduled, or run on another threadCommands are objects — objects can wait
You want a log/history of everything that happenedPush every executed command to a list
One action must fire from button, menu, and shortcutOne command, many invokers
You want macro operations (one click = many steps)A composite command holds child commands
A button does one fixed thing, forever, with no history neededA direct call or simple callback is enough
You are wrapping every tiny getter in a command "for purity"Pure ceremony; patterns must solve real pain
The operation cannot be reversed and you promise undo anyway⚠️Use snapshots (Memento) or do not promise undo

Place your own feature on this map. The more your actions need a "later" (undo later, run later, audit later), the stronger the case for slips:

Figure 9: A quick map for deciding whether Command fits your problem

⚠️ Common mistakes students make

⚠️

Mistake 1: Cooking inside the slip. Students often move real business logic into the command class. Wrong! Chef Lakshmi cooks; the slip only says what to cook. Commands should delegate to the receiver. If your command is 200 lines long, the chef's work has leaked into the paper.

⚠️

Mistake 2: Forgetting to clear the redo stack. After an undo, if the user performs a brand-new action, you must clear the redo stack. If you forget, the user can "redo" into a timeline that no longer exists, and your app's state becomes nonsense. This is the single most common undo/redo bug.

More traps to avoid:

  • Undoing a macro in forward order. A combo of [add biryani, add raita] must undo as [remove raita, remove biryani] — always reverse the order. Think of stacking plates: the last plate placed comes off first.
  • Commands sharing mutable state. If two commands both hold references to the same changing data, undoing one can corrupt the other's idea of "before". Keep each command's undo data inside itself, like each slip carrying its own table number.
  • An invoker that builds commands. Ravi should never decide the menu. Invokers receive commands from the client; they do not construct them.
  • Unbounded history. Pushing every command forever eats memory. Real editors cap history depth (for example, keep the last 100 commands). The rail has finite space; old slips go into the box.
  • Half-done executes. If execute() fails halfway, pushing it onto the undo stack anyway means undo() will reverse work that never fully happened. Push only after a successful execute.

👪 Compare with cousins

Command sits in a family of "wrap behavior in an object" patterns. Here is how to tell them apart:

PatternWhat is wrapped?Main question it answersSpecial power
CommandA request: what to do + receiver + params"Can I store/queue/undo this action?"History, queues, undo
StrategyAn algorithm: how to do one job"Which way should this task be done?"Swappable algorithms
Chain of ResponsibilityNothing — it links handlers"Who will handle this request?"Finds a handler at runtime
MementoA snapshot of state"What did things look like before?"State restore (pairs with Command for undo)
ObserverA subscription list"Who wants to know this happened?"Broadcast to many listeners

The Strategy confusion is the most common. Both look like "an object with one method". The test: Strategy answers how ("sort with quicksort or mergesort?"), Command answers what ("insert text", "delete row") and adds storage, queuing, and undo. In restaurant terms: a Strategy is Chef Lakshmi choosing between two recipes for the same dosa; a Command is the slip saying a dosa is wanted at table 4.

Also remember the friendly pairings: Command + Composite = macro commands (the Family Combo); Command + Memento = snapshot undo (photograph the kitchen board before cooking, restore the photo to undo); and a queue of commands is usually walked by an Iterator — the next lesson in this series.

📦 Quick revision box

+=====================================================================+
|                  COMMAND PATTERN — QUICK REVISION                   |
+=====================================================================+
| WHAT     : Turn a request into an object with execute() + undo().   |
| STORY    : Restaurant order slip. Aarav=Client, Ravi=Invoker,       |
|            Slip=Command, Chef Lakshmi/Kitchen=Receiver.             |
| ROLES    : Command | ConcreteCommand | Invoker | Receiver | Client  |
| RULE     : Commands DELEGATE to receivers; they do not do the work. |
| UNDO     : execute -> push UNDO | undo -> pop UNDO, push REDO       |
|            redo -> pop REDO, push UNDO | new action -> CLEAR REDO   |
| WINS     : undo/redo, queues, logging/replay, macros, one action    |
|            from many triggers                                       |
| RISKS    : class explosion, indirection, tricky undo state          |
| SEEN IN  : editor Ctrl+Z, job queues, Redux actions, WPF ICommand,  |
|            database transaction logs, event sourcing                |
| COUSIN   : Strategy = HOW to do a job; Command = WHAT to do, plus   |
|            store / queue / undo.                                    |
+=====================================================================+

🏋️ Practice exercise

Type these out yourself — typing builds memory that reading never will.

Task 1: TV remote. Build a Television receiver with on(), off(), setChannel(n). Create PowerOnCommand, PowerOffCommand, and SetChannelCommand (it must remember the previous channel so undo() can go back). Build a RemoteControl invoker with a history stack and an undoLast() button. Test: power on → channel 5 → channel 9 → undo (back to 5) → undo (does the TV remember what to reverse?).

Task 2: Text editor with redo. Build a Document receiver with insert(text) and deleteLast(count). Create a TypeCommand and give your invoker full undo and redo stacks, including the "clear redo on new action" rule. Test: type "Namaste", type " India", undo, undo, redo — the document should read "Namaste".

Task 3 (challenge): Order queue with replay. Make your restaurant waiter store every placed command in a log: Command[]. Add a replayDay(kitchen) function that builds a fresh kitchen and re-executes every logged command in order, rebuilding the kitchen board exactly. You have just implemented the core idea behind database crash recovery, Redux time-travel debugging, and event sourcing — Mr. D'Souza's midnight slip-counting, written in code.

Finish all three and you will never look at Ctrl+Z the same way again — behind that little shortcut is a rail of order slips, quietly waiting to be torn up. 🧾

Frequently asked questions

What is the Command pattern in simple words?
It wraps a request — what to do, on which object, with which details — into a standalone object with an execute() method. That object can be stored, queued, logged, passed around, or undone later.
Why is the Command pattern so good for undo and redo?
Because every action becomes an object that can also remember how to reverse itself. The app keeps executed commands on a history stack, so undo just pops the last command and calls its undo() method.
What are the invoker and the receiver in the Command pattern?
The invoker is the thing that triggers the command, like a button or a waiter — it only calls execute(). The receiver is the object that does the real work, like the document or the kitchen.
Where is the Command pattern used in real software?
Undo/redo in editors like Word, Google Docs, and VS Code; job queues that run tasks later; Redux actions in front-end apps; and GUI buttons and menu items in many frameworks.
How is Command different from Strategy?
Both wrap behavior in an object. Strategy swaps how one job is done (different algorithms for the same task). Command wraps what to do, so the request itself can be queued, logged, or undone.

Further reading

Related Lessons