OOD Fundamentals
OOP Foundations
SOLID Principles
Creational Patterns
Structural Patterns
Classic OOD Problems: Part 1
Classic OOD Problems: Part 2
Command and Chain of Responsibility
Why do GUI frameworks let you bind the same action to a menu item, a toolbar button, a keyboard shortcut, and a right-click menu without duplicating any logic? The answer is the Command pattern: you wrap each action in an object that knows how to execute itself. The button does not know what it does. It just calls execute() on whatever command object it holds.
This is the core insight. Instead of writing if button == "bold" then boldSelectedText() inside every UI element, you create a BoldCommand object and hand it to any invoker that needs it. The invoker (button, menu, shortcut) is completely decoupled from the receiver (the text editor engine). You can swap, queue, log, and undo commands because they are objects, not function calls buried in event handlers.
The Command pattern turns actions into first-class objects. Once an action is an object, you can store it in a list, serialize it to disk, send it over a network, replay it, or undo it. This single shift from 'call a function' to 'create an object that calls a function' unlocks undo/redo, macro recording, transaction logging, and distributed command queues.

The Four Participants
Every Command pattern implementation has the same four roles:
- Command interface defines
execute()andundo(). Every concrete command implements both. - ConcreteCommand (e.g.,
InsertTextCommand,DeleteTextCommand) holds a reference to the receiver and the parameters needed to perform and reverse the action. - Receiver is the object that does the actual work. A
TextEditorknows how to insert text at a position. The command tells it what to do. - Invoker triggers execution. A toolbar button, a keyboard shortcut handler, or an automated test. The invoker holds a command reference and calls
execute(). It never knows or cares what the command does.
A Text Editor Example
Consider a text editor with insert, delete, and bold operations. Without commands, each button handler contains the logic directly:
With the Command pattern, each action is a self-contained object:
The invoker is completely generic:
Adding a new action (italic, underline, find-and-replace) means writing one new Command subclass. The toolbar, menu, and shortcut system never change.
Why This Matters in Interviews
Interviewers ask about the Command pattern when they want to see if you understand decoupling. The pattern separates what happens from who triggers it and when it runs. If your design has a giant switch statement routing actions to logic, the Command pattern is the fix.
The Command pattern earns its place in real codebases the moment you need undo and redo. Every command knows two things: how to do the action and how to reverse it. That symmetry between execute() and undo() is what makes the entire system work.

The Two-Stack Model
The undo/redo mechanism uses two stacks:
- Undo stack: Every time a command executes, it is pushed onto the undo stack.
- Redo stack: When you undo a command, it is popped from the undo stack and pushed onto the redo stack.
- Redo after undo: Popping from the redo stack re-executes the command and pushes it back onto the undo stack.
- Critical rule: When a new command executes (not a redo), the redo stack is cleared. Once you undo three steps and then type a new character, those three undone commands are gone forever. This prevents a confusing branching history.
Implementation
Notice that redo() calls execute(), not a separate redo method. The command already knows how to do the action. Redo is just "do it again."
Composite Commands (Macros)
Sometimes a single user action involves multiple low-level operations. "Find and replace all" might execute 47 individual ReplaceCommand objects. The user expects Ctrl+Z to undo the entire find-and-replace, not each replacement one at a time.
A CompositeCommand (also called a macro command) groups multiple commands into one undo unit:
The undo reverses commands in the opposite order. If you inserted text at position 10 and then deleted text at position 20, undoing the delete must happen before undoing the insert, because the insert changed the positions that the delete relied on.
State Capture vs. Operation-Based Undo
There are two approaches to undo:
Operation-based (Command pattern): Each command stores the minimum information needed to reverse itself. InsertTextCommand stores the position and length so it can delete. DeleteTextCommand stores the deleted text so it can re-insert.
State-based (Memento pattern): Take a full snapshot of the state before each action. Undo restores the snapshot. Simple but memory-expensive for large states.
The Command pattern uses operation-based undo. For a text editor with a 10MB document, storing "inserted 5 characters at position 200" is far cheaper than storing a 10MB snapshot before every keystroke.
Practical Considerations
- Undo stack depth: Most applications cap the undo stack at 50-200 entries to limit memory usage.
- Non-undoable commands: Some commands (save file, send email) cannot be undone. These execute but do not push onto the undo stack.
- Command merging: Typing 10 characters one at a time produces 10 commands. Editors merge consecutive typing commands into a single "insert 'hello worl'" command to reduce undo noise.
The Command pattern solves "how do I encapsulate what to do." The Chain of Responsibility pattern solves a different problem: "who should handle this request when I don't know in advance?"
Think about tech support. You call with a problem. The L1 agent checks if it is a known issue with a scripted fix. If not, they escalate to L2 (experienced technician). If L2 cannot solve it, they escalate to L3 (specialist engineer). If L3 is stumped, it reaches the engineering manager. Each level either handles the request or passes it to the next. The caller does not choose which level handles the problem. The chain decides.
In interviews, the Chain of Responsibility pattern comes up whenever a request might be handled by one of several objects and the sender should not decide which one. If you hear 'escalation,' 'fallback,' 'middleware,' or 'filter chain,' think Chain of Responsibility.

Structure
The pattern has two participants:
- Handler interface defines
handle(request)andset_next(handler). Each handler either processes the request or delegates to the next handler in the chain. - ConcreteHandler implements the decision logic. It inspects the request, decides whether it can handle it, and either processes it or calls
self.next_handler.handle(request).
Implementation
Key Design Decisions
What if no handler processes the request? The chain must handle this gracefully. Options include: a default handler at the end that always accepts, returning a "not handled" response, or raising an exception. The worst choice is silently dropping the request.
Can a handler modify the request before passing it? Yes, and this is powerful. A logging handler might add a timestamp. An authentication handler might attach user identity. This transforms the chain from a "find the right handler" pattern into a "processing pipeline" pattern, which is the topic of the next section.
How do you order the chain? Typically from most specific to most general, or from cheapest check to most expensive. L1 support handles the easy cases quickly so L3 is not overwhelmed with password resets.
The Chain of Responsibility pattern has a powerful variant you use every day without realizing it: middleware pipelines. Every web framework (Express.js, Django, Flask, Spring) uses this pattern to process HTTP requests. The difference from the classic chain is that middleware does not stop at the first handler. Instead, every handler in the pipeline gets a chance to inspect, modify, or reject the request.

How Middleware Works
A typical web request passes through a pipeline like this:
- Authentication middleware checks the token. Invalid token? Return 401 immediately. Valid? Attach the user identity to the request and pass it forward.
- Rate limiting middleware checks if this client has exceeded their quota. Exceeded? Return 429. Under limit? Increment the counter and pass forward.
- Logging middleware records the request method, path, and timestamp. Always passes forward.
- Route handler processes the actual business logic and returns a response.
- The response travels back through the pipeline. Logging middleware records the response time. Rate limiting updates its counters. Authentication does nothing on the way back.
Each middleware can do three things: modify the request (add headers, parse body), short-circuit the chain (return an error without calling the next handler), or modify the response on the way back.
Implementation
Short-Circuiting
The most important middleware behavior is short-circuiting. When the auth middleware returns a 401, it never calls self.call_next(). The rate limiter, logger, and route handler never see the request. This is both a performance optimization (why parse the body if the token is invalid?) and a security boundary (unauthenticated requests never reach business logic).
Pipeline Ordering Matters
The order of middleware is a design decision with real consequences:
- Auth before rate limiting: Rate limits apply per authenticated user, not per IP. More accurate but requires auth to run first.
- Rate limiting before auth: Protects the auth service from brute-force attacks. A flood of invalid tokens hits the rate limiter before auth has to validate them.
- Logging first: Captures all requests including rejected ones. Useful for debugging.
- Logging last: Only captures successful requests. Cleaner logs but blind to failures.
There is no universally correct order. The right order depends on your threat model and observability requirements.
Interviews often ask you to compare behavioral patterns. Command, Chain of Responsibility, and Strategy all deal with "how to handle an action," but they answer different questions. Confusing them signals that you memorized pattern names without understanding the problems they solve.
A common interview mistake is saying 'Command and Strategy are basically the same because both encapsulate behavior in an object.' They are not. Command encapsulates a specific action with its parameters to be executed later. Strategy encapsulates an algorithm to be selected at runtime. Command is a noun (the action). Strategy is an adjective (the approach).
The Three Patterns Compared
Command answers: WHAT should happen?
A command object encapsulates a specific action (insert text, delete row, send email) along with all the parameters needed to execute and reverse it. The focus is on capturing the action as a self-contained, storable, undoable unit. Commands are typically executed once and kept for undo/logging.
Chain of Responsibility answers: WHO should handle this?
A request enters a chain of handlers. Each handler decides if it can process the request or should pass it along. The sender does not choose the handler. The chain's structure determines routing. The focus is on decoupling the sender from the receiver.
Strategy answers: HOW should this be done?
A strategy encapsulates an algorithm (sort with quicksort vs. mergesort, compress with gzip vs. zstd, price with flat-rate vs. tiered). The client selects the strategy based on context. The focus is on interchangeable algorithms behind a common interface.
Decision Framework
Ask yourself these questions when choosing a pattern:
- Do you need to store, queue, undo, or replay an action? Use Command.
- Do you need to route a request to one of several handlers without the sender knowing which? Use Chain of Responsibility.
- Do you need to swap between different algorithms for the same task? Use Strategy.
Transaction Logging with Commands
One of the most powerful applications of the Command pattern is transaction logging. Every command that executes is serialized and appended to a log file. If the system crashes, you replay the log from the last checkpoint to recover the state.
This is exactly how databases implement write-ahead logging (WAL). Each SQL statement is a command. The WAL is the command log. Crash recovery replays the WAL. Event sourcing in distributed systems follows the same principle: store the commands (events), not the state. Derive the state by replaying commands.
Practice Problems
Apply these patterns to concrete implementations. The first problem combines Command with undo/redo stacks. The second builds a Chain of Responsibility middleware pipeline. The third uses Command for transaction logging with crash recovery.
Loading problem...
Loading problem...
Loading problem...