Skip to content
LLD Learn/Behavioral Patterns
Browsing as a guest. Sign in to save your progress and earn XP as you complete chapters.

Chain of Responsibility & Template Method

7 min read

You'll learn to

  • -Build a chain of handlers where each either processes a request or passes it along, as in a middleware/logging pipeline
  • -Use Template Method to fix an algorithm's skeleton in a base class while letting subclasses override individual steps

These two patterns are grouped together because both are about structuring a multi-step process, but from opposite directions: Chain of Responsibility lets any number of independent handlers each decide whether to act, in sequence. Template Method fixes the overall sequence in one place and lets subclasses customize individual steps.

Chain of Responsibility: A Pipeline of Independent Handlers

Each handler in the chain gets a chance to process a request, and either handles it, passes it to the next handler, or does both. A logging framework is the canonical example: a log message passes through a chain of handlers (DebugHandler, InfoHandler, ErrorHandler), each checking whether it is responsible for this log level, with the sender never needing to know which handler ultimately processes the message.

Each handler decides independently whether to act or pass along
class Handler(ABC):
    def __init__(self):
        self._next: Handler | None = None

    def set_next(self, handler: "Handler") -> "Handler":
        self._next = handler
        return handler                    # allows chaining: h1.set_next(h2).set_next(h3)

    def handle(self, request: str) -> None:
        if self._next:
            self._next.handle(request)    # pass along if this handler doesn't fully handle it

class AuthenticationHandler(Handler):
    def handle(self, request: str) -> None:
        print("Checking authentication...")
        if not request.startswith("Authorization: "):
            print("Rejected: no credentials")
            return                        # short-circuits - never calls super().handle()
        super().handle(request)           # only passes along once authenticated

class RateLimitHandler(Handler):
    def handle(self, request: str) -> None:
        print("Checking rate limit...")
        super().handle(request)

class LoggingHandler(Handler):
    def handle(self, request: str) -> None:
        print(f"Logging request: {request}")
        super().handle(request)

chain = AuthenticationHandler()
chain.set_next(RateLimitHandler()).set_next(LoggingHandler())
chain.handle("GET /orders")                          # rejected - never reaches RateLimitHandler or LoggingHandler
chain.handle("Authorization: Bearer xyz GET /orders")  # passes auth, flows through all three

This is the exact structural shape behind most web framework middleware pipelines: each middleware either short-circuits the request (AuthenticationHandler above rejects an unauthenticated call by simply returning instead of calling super().handle()) or passes it to the next one, and adding a new cross-cutting concern (rate limiting, logging, compression) means inserting one new handler into the chain, with zero changes to the others.

Template Method: One Fixed Skeleton, Customizable Steps

Template Method defines the skeleton of an algorithm in a base class method, deferring some steps to subclasses - the overall sequence never changes, but individual steps do. A data-processing pipeline that always reads, transforms, then writes data, but where "read" and "write" differ by source (CSV vs. JSON), is a natural fit.

The algorithm's shape is fixed; the steps are overridden
class DataProcessor(ABC):
    def process(self) -> None:                # the template method - never overridden
        data = self.read_data()
        transformed = self.transform(data)
        self.write_data(transformed)

    @abstractmethod
    def read_data(self) -> list: ...
    def transform(self, data: list) -> list:   # has a sensible default, can still be overridden
        return data
    @abstractmethod
    def write_data(self, data: list) -> None: ...

class CSVProcessor(DataProcessor):
    def read_data(self) -> list: return read_csv_file()
    def write_data(self, data: list) -> None: write_csv_file(data)

The interview-relevant distinction between the two: Chain of Responsibility is about which handler(s) run, decided dynamically at runtime by each handler. Template Method is about which steps run, fixed at compile time by the base class - only the content of each step varies.

Interview Signal is part of Pro

See a real weak answer next to a real strong one for this exact topic.

Quiz is part of Pro

Test what you just read with a short quiz, and bank the XP.

ScaleDojo Logo
Initializing ScaleDojo