Skip to main content
Low-Level Design·Intermediate

Behavioral Patterns: Observer, Command, Strategy, Template Method & Chain of Responsibility

Behavioral design patterns define how objects communicate and delegate work. Master Observer (event notification), Command (encapsulate requests), Strategy (interchangeable algorithms), Template Method (algorithm skeleton with extension points), and Chain of Responsibility (pipeline processing) — with Java examples and real interview applications.

35 min read 8 sections 5 interview questions
Behavioral PatternsObserverCommandStrategyTemplate MethodChain of ResponsibilityDesign PatternsGang of FourEvent SystemNotificationPipelineAlgorithm FamilyJavaDelegationLoose Coupling

Behavioral Patterns: How Objects Communicate

Behavioral patterns address how objects interact and distribute responsibilities. While structural patterns focus on what objects are made of, behavioral patterns focus on how they talk to each other. The recurring challenge they solve is: how do you decouple the sender of a request from the receiver, so either side can change without affecting the other?

The key insight shared across behavioral patterns is delegation: the caller doesn't know the specific handler, just an interface. This enables runtime flexibility (swap strategies, add observers, change the processing chain) without modifying the caller's code.

IMPORTANT

What Earns Each Level

6/10: Names patterns. Provides textbook definitions. Implements a working but rigid example.

8/10: Selects the right pattern based on the problem description. Implements cleanly with correct abstraction level. Explains what the pattern decouples. Knows when NOT to use the pattern.

10/10: Chains patterns together (Command + Observer for a UI event system). Connects to real frameworks: Observer = Spring ApplicationEvent / RxJava Observable / GUI listeners; Strategy = Java Comparator; Template Method = Spring's JdbcTemplate; Chain of Responsibility = HTTP middleware, Java servlet filters. Identifies when adding a behavioral pattern increases complexity more than it reduces coupling.

The Five Core Behavioral Patterns

01

Observer — Notify multiple dependents of state changes

When one object changes state, all dependent objects are notified automatically. Subject maintains a list of Observers and calls update() on each when it changes. Observer registers/unregisters with the Subject. Decouples Subject from its dependents — Subject doesn't know how many or what type of Observers exist. Signal: 'When X changes, multiple independent components should react.' Examples: event system, stock ticker, UI data binding, notification service. Java: EventListener, RxJava Observable, Spring ApplicationEvent.

02

Command — Encapsulate a request as an object

Package a request (method call + parameters) into an object with an execute() method. Benefits: decouple sender from receiver, queue/log/retry commands, support undo (store a reverse command). Signal: 'We need to queue operations, support undo, or log all actions.' Examples: undo/redo in a text editor, task queue, remote procedure, macro recording. The Command object knows the receiver; the invoker knows only the Command interface.

03

Strategy — Make algorithms interchangeable

Define a family of algorithms, encapsulate each, and make them interchangeable. Client selects the strategy at runtime or construction time. Eliminates large conditional logic (switch on sort type → inject the right sorter). Signal: 'There are multiple ways to do X, and we want to switch between them.' Examples: sorting algorithms, payment methods, routing algorithms, pricing strategies, compression methods. Java: Comparator is the canonical Strategy example.

04

Template Method — Define the algorithm skeleton, let subclasses fill in steps

Abstract class defines the steps of an algorithm. Some steps have default implementations; others are abstract (left to subclasses). The overall structure (the template method) is fixed; specific steps vary. Provides the 'Hollywood Principle': don't call us, we'll call you — the base class calls the subclass's overridden steps. Signal: 'All variants of this process share the same overall steps, but differ in specific sub-steps.' Examples: data parsers (read → parse → validate → save), Spring's JdbcTemplate, test setUp/tearDown.

05

Chain of Responsibility — Pass request through a chain of handlers

Each handler decides whether to process the request or pass it to the next handler in the chain. Decouples sender from concrete handler; handlers can be reordered, added, or removed at runtime. Signal: 'Multiple objects may handle a request; we want to avoid hard-coding which one.' Examples: HTTP middleware (auth → rate-limit → log → handle), exception handling (try-catch chain), customer support escalation (L1 → L2 → L3). Each handler has a reference to the next handler.

Observer Pattern: Event Notification System

Rendering diagram...

Implementation Templates

Observer:

interface Observer { void update(Event event); }

class EventBus {
    private final Map<String, List<Observer>> listeners = new HashMap<>();

    public void subscribe(String event, Observer observer) {
        listeners.computeIfAbsent(event, k -> new ArrayList<>()).add(observer);
    }

    public void publish(String event, Event data) {
        listeners.getOrDefault(event, List.of()).forEach(o -> o.update(data));
    }
}
// Usage: eventBus.publish("ORDER_PLACED", orderEvent);

Strategy — Payment processing:

interface PaymentStrategy { void pay(double amount); }

class CreditCardPayment implements PaymentStrategy {
    public void pay(double amount) { /* charge credit card */ }
}
class CryptoPayment implements PaymentStrategy {
    public void pay(double amount) { /* broadcast transaction */ }
}

class Checkout {
    private PaymentStrategy strategy;
    public void setStrategy(PaymentStrategy s) { this.strategy = s; }
    public void checkout(double total) { strategy.pay(total); }  // no if-else!
}

Command — Text Editor with Undo:

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

class InsertTextCommand implements Command {
    private final Document doc;
    private final String text;
    private final int position;

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

class CommandHistory {
    private final Deque<Command> history = new ArrayDeque<>();
    public void execute(Command cmd) { cmd.execute(); history.push(cmd); }
    public void undo() { if (!history.isEmpty()) history.pop().undo(); }
}

Chain of Responsibility — HTTP Middleware:

abstract class Middleware {
    private Middleware next;
    public Middleware setNext(Middleware next) { this.next = next; return next; }
    public abstract boolean handle(HttpRequest req, HttpResponse resp);

    protected boolean passToNext(HttpRequest req, HttpResponse resp) {
        if (next == null) return true;  // end of chain: request handled
        return next.handle(req, resp);
    }
}

class AuthMiddleware extends Middleware {
    public boolean handle(HttpRequest req, HttpResponse resp) {
        if (!req.hasValidToken()) { resp.setStatus(401); return false; }
        return passToNext(req, resp);   // pass to next handler
    }
}

class RateLimitMiddleware extends Middleware {
    public boolean handle(HttpRequest req, HttpResponse resp) {
        if (isRateLimited(req.getIp())) { resp.setStatus(429); return false; }
        return passToNext(req, resp);
    }
}
// Chain: auth.setNext(rateLimit).setNext(logger).setNext(handler);

Template Method — Data Parser:

abstract class DataParser {
    // Template method — defines the algorithm skeleton
    public final void parse(String source) {
        String raw = read(source);      // step 1: always the same
        Object data = parse(raw);        // step 2: varies by format
        validate(data);                  // step 3: may vary
        save(data);                      // step 4: always the same
    }

    protected abstract String read(String source);  // must override
    protected abstract Object parse(String raw);    // must override
    protected void validate(Object data) {}          // optional hook
    private void save(Object data) { db.save(data); } // fixed
}

class JsonParser extends DataParser {
    protected String read(String source) { return Files.readString(Path.of(source)); }
    protected Object parse(String raw) { return JSON.parse(raw); }
}

Pattern Selection Guide

Problem signalPatternWhat it decouples
'Multiple components need to react when X changes'ObserverSubject from its dependents
'Need to queue, undo, or log operations'CommandSender from receiver; request from execution
'Multiple algorithms for the same task; want to switch at runtime'StrategyAlgorithm from the context that uses it
'Same overall process, different specific steps'Template MethodAlgorithm skeleton from step implementations
'Multiple handlers may process a request; want to add/remove handlers'Chain of ResponsibilitySender from concrete handler; handlers from each other
'Need to notify exactly one subscriber (not all)'Observer variant or direct callbackObserver notifies ALL subscribers — for one-to-one, use a callback/handler directly
⚠ WARNING

Common Pitfalls

Observer — memory leaks. If an Observer registers with a Subject but forgets to unsubscribe, the Subject holds a reference to the Observer forever — preventing garbage collection. In Java: use WeakReference for Observer storage, or ensure unsubscribe() is called in cleanup/destructor. This is a classic Android memory leak.

Observer — notification order. Don't assume Observers are notified in registration order. If Order matters, it's not Observer — use a sorted list or explicit sequencing.

Strategy — state in strategy. If a Strategy object holds mutable state, it can't be safely shared across contexts. Either make strategies stateless (can be singletons/shared), or create a new instance per context.

Template Method — inflexible hierarchy. Template Method uses inheritance. If the algorithm skeleton needs to change, the base class changes — affecting all subclasses. Prefer Strategy (composition) when the skeleton itself may vary. Rule: "Template Method when the skeleton is stable; Strategy when the algorithm as a whole may swap."

Command — CQRS confusion. In CQRS (Command Query Responsibility Segregation), "Command" means a write operation. The Command design pattern means encapsulating a request. These are different uses of the word. Don't confuse them in an interview.

TIP

Interview Delivery Summary

Observer pattern signal: "When X changes, multiple things happen." Chain of Responsibility signal: "A request passes through a pipeline of handlers — each can handle or pass it on."

Strategy vs Template Method: Strategy uses composition (inject the algorithm); Template Method uses inheritance (override the step). Prefer Strategy for runtime flexibility. Use Template Method when the skeleton is fixed and variations are in specific named steps.

Command is the gateway to undo/redo, command queuing, and audit logging. Always mention all three benefits when you propose Command — interviewers want to know you're not just implementing execute(), but thinking about undo(), logging, and queue.

Real-world links earn points: Observer = Java's EventListener / RxJava / React's useEffect dependency tracking. Strategy = Java's Comparator. Chain of Responsibility = Express.js middleware / Servlet filters.

Interview Questions

Click to reveal answers