MindForge
MindForge
DashboardHelp & Support
Dashboard
MindForge

Interview Preparation Bootcamp

Course progress

0%
0 of 177 modules complete
Start Here0/1
  • How to Use This Course
Data Structures & Algorithms0/27
  • Arrays and Hashing
  • Two Pointers
  • Sliding Window
  • Sliding Window: Harder Problems
  • Binary Search
  • Stacks
  • Practice Test: Arrays, Pointers, Windows, Search and Stacks
  • Trees: Traversals and Core Patterns
  • Binary Search Trees and Rebuilding a Tree
  • Tries (Prefix Trees)
  • Heaps and Priority Queues
  • Graphs: BFS and DFS
  • Topological Sort and Cycle Detection
  • Union-Find (Disjoint Set Union)
  • Practice Test: Trees, Heaps and Graphs
  • Dynamic Programming Basics
  • Dynamic Programming: Two Dimensions
  • Dynamic Programming: Hard String Matching
  • Backtracking
  • Backtracking: Constraint Satisfaction
  • Greedy Algorithms
  • Interval Problems
  • Bit Manipulation
  • Math and Geometry
  • String Manipulation
  • Pattern Recall and Mock Coding Rounds
  • Practice Test: DP, Backtracking, Greedy and More

System Design

Foundations0/16
  • The System Design Interview Framework
  • Back-of-the-Envelope Estimation
  • Networking and Protocols for Design
  • API Design and Idempotency
  • Load Balancing, Proxies, and Scaling Out
  • Caching and CDNs
  • Choosing a Database: Storage, Indexes and Transactions
  • Replication, Partitioning, and Sharding
  • CAP, PACELC, and Levels of Consistency
  • Messaging, Queues, and Event-Driven Architecture
  • Distributed Transactions and Data Integrity
  • Coordination, Consensus, and Distributed Locks
  • Reliability, Resilience, and Rate Limiting
  • Observability, Security, and Cost
  • System Design Cheat Sheet
  • Practice Test: System Design Foundations
Case Studies0/23
  • Design a URL Shortener
  • Design a Rate Limiter
  • Design a Notification Service
  • Design a Job Queue System
  • Design a Chat App (WhatsApp)
  • Design an Analytics Pipeline
  • Design a Multi-tenant SaaS Architecture
  • Design a Social Media Feed (Twitter/X)
  • Design a Search Autocomplete System
  • Design a File Storage Service (Dropbox)
  • Design Google Docs
  • Design a Code Review System
  • Design Netflix
  • Design YouTube
  • Design Spotify
  • Design Uber/Lyft
  • Design Airbnb
  • Design Ticketmaster (Eventbrite)
  • Design an E-commerce Cart, Checkout, and Catalog
  • Design a Payment System
  • Design a Logging and Monitoring System
  • Mock Design Rounds
  • Practice Test: System Design Case Studies
Low-Level Design0/19
  • The LLD Interview Framework and UML
  • OOP Foundations for Design
  • SOLID Principles
  • Creational Patterns
  • Structural Patterns
  • Behavioral Patterns I — Strategy, Observer, Command, State, Template Method
  • Behavioral Patterns II and Anti-Patterns
  • Concurrency in Low-Level Design
  • Design a Parking Lot
  • Design an Elevator System
  • Design a Movie Ticket Booking System
  • Design an Expense-Sharing App (Splitwise)
  • Design a Vending Machine and an ATM
  • Design a Rate Limiter and a Logging Framework
  • Design a Notification Service and an In-Memory Cache
  • Design Games — Tic-Tac-Toe, Snake and Ladder, Chess
  • Database Design for LLD
  • LLD Cheat Sheet and Recall Drill
  • Practice Test: Low-Level Design

Backend

Python0/5
  • How Python Runs Your Code
  • Functions Deep Dive: Closures, Generators, Context Managers, Scope
  • Classes Deep Dive: Method Types, MRO, Metaclasses
  • Standard Library and Serialization
  • Practice Test: Python
Django0/6
  • Django Request Lifecycle
  • Lab: Django Middleware — Request ID & Response Time
  • Django ORM and Querysets
  • Django's Cache Framework
  • Practice Test: Django
  • Django in Production: Debugging Labs
FastAPI0/5
  • FastAPI, ASGI, and Async
  • FastAPI Dependency Injection
  • FastAPI Background Tasks
  • Lab: FastAPI Async — Concurrent Aggregation
  • Practice Test: FastAPI
Databases (PostgreSQL)0/6
  • Indexing
  • Query Optimization
  • Transactions and Isolation
  • Migrations and Schema Changes
  • Hierarchical Data and Safe Concurrent Updates
  • Practice Test: Databases
Caching, Queues & Distributed Systems0/8
  • Redis Caching
  • Redis Advanced Patterns
  • Celery and Background Workers
  • Kafka and Message Queues
  • Async Pipelines
  • Distributed Systems Basics
  • Distributed Locks
  • Practice Test: Caching, Queues and Distributed Systems
APIs, Security & Deployment0/9
  • REST API Design
  • API Versioning and Error Handling
  • API Security and Authentication
  • WebSockets and Real-Time Systems
  • Docker and Containers
  • File Storage with S3
  • Testing Strategies
  • Mock Backend Round
  • Practice Test: APIs, Security and Deployment
Frontend0/38
  • HTML Document Structure and Semantics
  • CSS Box Model, Flexbox and Grid
  • CSS Architecture and Theming
  • CSS and Rendering Performance
  • Variables, Scope, Hoisting, and Truthiness
  • Functions, this, and Binding
  • Objects and Prototypal Inheritance
  • JavaScript Utility Patterns: Cloning, Caching, and Memory
  • Promises From Scratch
  • The Event Loop and Browser Rendering Pipeline
  • The DOM: Nodes, Events, and Traversal
  • Practice Test: HTML, CSS and JavaScript
  • React Rendering Fundamentals
  • React Hooks Internals
  • React Performance
  • Lab: React — Stateful Counter Component
  • State Management
  • State Machines in React
  • React Custom Hooks and Composition Patterns
  • TypeScript for React
  • React Testing
  • Practice Test: React
  • HTTP and Caching
  • Network Performance and Web Vitals
  • Code Splitting and Bundles
  • Virtualization
  • GraphQL and Data Fetching
  • Web Security
  • WebSockets and Real-time
  • SSR and Next.js
  • Micro-frontends
  • Build Tools and Bundling
  • Accessibility
  • Error Handling
  • Internationalization
  • Animation
  • Rapid Recall and Mock Frontend Rounds
  • Practice Test: Performance, Architecture and Production
Behavioral & Interview Day0/14
  • Resume and Project Walkthroughs
  • The STAR Method and Refining Your Stories
  • Conflict and Difficult Coworkers
  • Failures, Incidents, and Obstacles
  • Leadership, Mentorship, and Ownership
  • Teamwork, Communication, and Feedback
  • Growth and Handling Ambiguity
  • Career Goals, Why This Company, and Negotiation
  • Situational Questions
  • Company Research
  • Mock Behavioral Rounds
  • Interview Day
  • After the Interview
  • Practice Test: Behavioral and Interview Day
Behavioral Patterns II and Anti-Patterns
Course contents

Interview Preparation Bootcamp

Course progress

0%
0 of 177 modules complete
Start Here0/1
  • How to Use This Course
Data Structures & Algorithms0/27
  • Arrays and Hashing
  • Two Pointers
  • Sliding Window
  • Sliding Window: Harder Problems
  • Binary Search
  • Stacks
  • Practice Test: Arrays, Pointers, Windows, Search and Stacks
  • Trees: Traversals and Core Patterns
  • Binary Search Trees and Rebuilding a Tree
  • Tries (Prefix Trees)
  • Heaps and Priority Queues
  • Graphs: BFS and DFS
  • Topological Sort and Cycle Detection
  • Union-Find (Disjoint Set Union)
  • Practice Test: Trees, Heaps and Graphs
  • Dynamic Programming Basics
  • Dynamic Programming: Two Dimensions
  • Dynamic Programming: Hard String Matching
  • Backtracking
  • Backtracking: Constraint Satisfaction
  • Greedy Algorithms
  • Interval Problems
  • Bit Manipulation
  • Math and Geometry
  • String Manipulation
  • Pattern Recall and Mock Coding Rounds
  • Practice Test: DP, Backtracking, Greedy and More

System Design

Foundations0/16
  • The System Design Interview Framework
  • Back-of-the-Envelope Estimation
  • Networking and Protocols for Design
  • API Design and Idempotency
  • Load Balancing, Proxies, and Scaling Out
  • Caching and CDNs
  • Choosing a Database: Storage, Indexes and Transactions
  • Replication, Partitioning, and Sharding
  • CAP, PACELC, and Levels of Consistency
  • Messaging, Queues, and Event-Driven Architecture
  • Distributed Transactions and Data Integrity
  • Coordination, Consensus, and Distributed Locks
  • Reliability, Resilience, and Rate Limiting
  • Observability, Security, and Cost
  • System Design Cheat Sheet
  • Practice Test: System Design Foundations
Case Studies0/23
  • Design a URL Shortener
  • Design a Rate Limiter
  • Design a Notification Service
  • Design a Job Queue System
  • Design a Chat App (WhatsApp)
  • Design an Analytics Pipeline
  • Design a Multi-tenant SaaS Architecture
  • Design a Social Media Feed (Twitter/X)
  • Design a Search Autocomplete System
  • Design a File Storage Service (Dropbox)
  • Design Google Docs
  • Design a Code Review System
  • Design Netflix
  • Design YouTube
  • Design Spotify
  • Design Uber/Lyft
  • Design Airbnb
  • Design Ticketmaster (Eventbrite)
  • Design an E-commerce Cart, Checkout, and Catalog
  • Design a Payment System
  • Design a Logging and Monitoring System
  • Mock Design Rounds
  • Practice Test: System Design Case Studies
Low-Level Design0/19
  • The LLD Interview Framework and UML
  • OOP Foundations for Design
  • SOLID Principles
  • Creational Patterns
  • Structural Patterns
  • Behavioral Patterns I — Strategy, Observer, Command, State, Template Method
  • Behavioral Patterns II and Anti-Patterns
  • Concurrency in Low-Level Design
  • Design a Parking Lot
  • Design an Elevator System
  • Design a Movie Ticket Booking System
  • Design an Expense-Sharing App (Splitwise)
  • Design a Vending Machine and an ATM
  • Design a Rate Limiter and a Logging Framework
  • Design a Notification Service and an In-Memory Cache
  • Design Games — Tic-Tac-Toe, Snake and Ladder, Chess
  • Database Design for LLD
  • LLD Cheat Sheet and Recall Drill
  • Practice Test: Low-Level Design

Backend

Python0/5
  • How Python Runs Your Code
  • Functions Deep Dive: Closures, Generators, Context Managers, Scope
  • Classes Deep Dive: Method Types, MRO, Metaclasses
  • Standard Library and Serialization
  • Practice Test: Python
Django0/6
  • Django Request Lifecycle
  • Lab: Django Middleware — Request ID & Response Time
  • Django ORM and Querysets
  • Django's Cache Framework
  • Practice Test: Django
  • Django in Production: Debugging Labs
FastAPI0/5
  • FastAPI, ASGI, and Async
  • FastAPI Dependency Injection
  • FastAPI Background Tasks
  • Lab: FastAPI Async — Concurrent Aggregation
  • Practice Test: FastAPI
Databases (PostgreSQL)0/6
  • Indexing
  • Query Optimization
  • Transactions and Isolation
  • Migrations and Schema Changes
  • Hierarchical Data and Safe Concurrent Updates
  • Practice Test: Databases
Caching, Queues & Distributed Systems0/8
  • Redis Caching
  • Redis Advanced Patterns
  • Celery and Background Workers
  • Kafka and Message Queues
  • Async Pipelines
  • Distributed Systems Basics
  • Distributed Locks
  • Practice Test: Caching, Queues and Distributed Systems
APIs, Security & Deployment0/9
  • REST API Design
  • API Versioning and Error Handling
  • API Security and Authentication
  • WebSockets and Real-Time Systems
  • Docker and Containers
  • File Storage with S3
  • Testing Strategies
  • Mock Backend Round
  • Practice Test: APIs, Security and Deployment
Frontend0/38
  • HTML Document Structure and Semantics
  • CSS Box Model, Flexbox and Grid
  • CSS Architecture and Theming
  • CSS and Rendering Performance
  • Variables, Scope, Hoisting, and Truthiness
  • Functions, this, and Binding
  • Objects and Prototypal Inheritance
  • JavaScript Utility Patterns: Cloning, Caching, and Memory
  • Promises From Scratch
  • The Event Loop and Browser Rendering Pipeline
  • The DOM: Nodes, Events, and Traversal
  • Practice Test: HTML, CSS and JavaScript
  • React Rendering Fundamentals
  • React Hooks Internals
  • React Performance
  • Lab: React — Stateful Counter Component
  • State Management
  • State Machines in React
  • React Custom Hooks and Composition Patterns
  • TypeScript for React
  • React Testing
  • Practice Test: React
  • HTTP and Caching
  • Network Performance and Web Vitals
  • Code Splitting and Bundles
  • Virtualization
  • GraphQL and Data Fetching
  • Web Security
  • WebSockets and Real-time
  • SSR and Next.js
  • Micro-frontends
  • Build Tools and Bundling
  • Accessibility
  • Error Handling
  • Internationalization
  • Animation
  • Rapid Recall and Mock Frontend Rounds
  • Practice Test: Performance, Architecture and Production
Behavioral & Interview Day0/14
  • Resume and Project Walkthroughs
  • The STAR Method and Refining Your Stories
  • Conflict and Difficult Coworkers
  • Failures, Incidents, and Obstacles
  • Leadership, Mentorship, and Ownership
  • Teamwork, Communication, and Feedback
  • Growth and Handling Ambiguity
  • Career Goals, Why This Company, and Negotiation
  • Situational Questions
  • Company Research
  • Mock Behavioral Rounds
  • Interview Day
  • After the Interview
  • Practice Test: Behavioral and Interview Day
Browsing without an account — progress is saved in this browser only.Sign in to save it

Behavioral Patterns II and Anti-Patterns

Reading45 min

The remaining behavioral patterns come up less often than Strategy and Observer, but each one is the go-to answer for a specific kind of problem. Chain of Responsibility, especially, is what nearly every piece of middleware, logging library, and approval workflow you've ever used is quietly built from. This lesson finishes the pattern list, then moves on to anti-patterns — because spotting a bad design out loud is worth just as much in an interview as building a good one.

Chain of Responsibility

The idea: pass a request down a line of handlers until one of them deals with it (or every handler has had a turn). Each handler in the chain decides whether to act, whether to pass the request further, or both.

Python
from abc import ABC, abstractmethod

class Handler(ABC):
    def __init__(self): self._next: "Handler | None" = None

    def set_next(self, nxt: "Handler") -> "Handler":
        self._next = nxt
        return nxt                                  # returns nxt, so chains read left-to-right

    def handle(self, request: dict) -> str | None:
        result = self.process(request)
        if result is not None:
            return result                           # this handler dealt with it — stop here
        return self._next.handle(request) if self._next else None

    @abstractmethod
    def process(self, request: dict) -> str | None: ...

class AuthHandler(Handler):
    def process(self, request):
        return None if request.get("user") else "401 unauthenticated"

class RateLimitHandler(Handler):
    def __init__(self, limit: int):
        super().__init__()
        self.limit, self.seen = limit, {}
    def process(self, request):
        user = request["user"]
        self.seen[user] = self.seen.get(user, 0) + 1
        return "429 rate limited" if self.seen[user] > self.limit else None

class ValidationHandler(Handler):
    def process(self, request):
        return None if request.get("body") else "400 missing body"

class BusinessHandler(Handler):
    def process(self, request):
        return f"200 processed {request['body']} for {request['user']}"


chain = AuthHandler()
chain.set_next(RateLimitHandler(limit=2)).set_next(ValidationHandler()).set_next(BusinessHandler())

assert chain.handle({}) == "401 unauthenticated"
assert chain.handle({"user": "asha", "body": "x"}).startswith("200")
assert chain.handle({"user": "asha", "body": "y"}).startswith("200")
assert chain.handle({"user": "asha", "body": "z"}) == "429 rate limited"
print("chain result:", chain.handle({"user": "ravi"}))

You've already used this everywhere: HTTP middleware in Express, Django, or Chi, servlet filters, logging libraries (a DEBUG handler passes upward until something matches the current level), approval workflows (manager, then director, then VP, depending on amount), and even how a program handles exceptions internally.

Two things worth naming: the chain can be reconfigured while the program runs — reorder, add, or remove a handler without touching any other one — and the sender has no idea which handler will actually respond, which is exactly where the decoupling this pattern buys you comes from. The risk is that a request can reach the end of the chain with nobody having dealt with it, so always design that final fallback on purpose.

Remember: always decide on purpose what happens when a request reaches the end of the chain with nobody handling it.

Sign in to try this knowledge check.

Iterator, Mediator, and Memento

Iterator — the idea: walk through a collection without exposing how it's actually stored inside. You already use this every time you write a for loop. The real design point is that the code calling it never has to know whether it's looping over an array, a tree, or a paginated API.

Python
class PaginatedFeed:
    """Looks like a plain sequence to callers; secretly fetches one page at a time."""
    def __init__(self, pages: list[list[str]]):
        self._pages = pages

    def __iter__(self):
        for page in self._pages:                  # imagine each page were a network call
            for item in page:
                yield item                        # a generator basically IS the iterator here


feed = PaginatedFeed([["a", "b"], ["c"], ["d", "e"]])
assert list(feed) == ["a", "b", "c", "d", "e"]
assert sum(1 for _ in feed) == 5                  # can be looped over again
print("streamed lazily:", [x for x in feed][:3])

The real value here: swapping the storage from a plain list to a database cursor changes nothing for whoever's calling it. In Python, generators make this pattern nearly invisible — it's worth mentioning that out loud. In Java it's explicit, through Iterable/Iterator.

Mediator — the idea: put complicated many-to-many communication in one central place, so objects stop talking to each other directly. Without it, n components that all need to talk to each other could need up to n(n−1)/2 separate connections. With a mediator, every one of them only needs to know about the mediator itself.

Python
class ChatRoom:                                    # the mediator
    def __init__(self): self._members: dict[str, "User"] = {}

    def join(self, user: "User") -> None:
        self._members[user.name] = user
        user.room = self

    def send(self, sender: str, text: str) -> None:
        for name, user in self._members.items():
            if name != sender:                     # users never reference each other directly
                user.receive(sender, text)

class User:
    def __init__(self, name: str):
        self.name, self.room, self.inbox = name, None, []
    def say(self, text: str) -> None: self.room.send(self.name, text)
    def receive(self, sender: str, text: str) -> None: self.inbox.append((sender, text))


room = ChatRoom()
asha, ravi, meera = User("asha"), User("ravi"), User("meera")
for u in (asha, ravi, meera):
    room.join(u)

asha.say("hello")
assert ravi.inbox == [("asha", "hello")] and meera.inbox == [("asha", "hello")]
assert asha.inbox == []                            # the sender doesn't get their own message back
print("mediated:", ravi.inbox)

Worth stating as a warning: the mediator can turn into a god object all on its own. It quietly absorbs coordination logic from everywhere and grows without limit. Keep it focused on routing and coordination, and leave the actual domain rules with the participants. Air traffic control, dialog coordination in a UI, and — at a much bigger scale — a message broker are all mediators.

Memento — the idea: capture and later restore an object's state, without exposing what's inside it. This is the pattern behind undo, checkpoints, and save games, and it fits naturally alongside Command, which stores the memento it needs in order to reverse itself. The key detail: the memento is opaque to everyone except the object that made it — whoever's holding it can pass it around, but can't peek inside or change it, which is what keeps encapsulation intact.

Remember: a mediator that starts holding domain rules, not just routing, has already become the god object it was meant to prevent.

Sign in to try this knowledge check.

Visitor and Interpreter

Visitor — the idea: add new operations to a fixed set of types without touching the classes in it.

Here's the problem it solves: you have a fixed set of node types (a syntax tree, a document tree, a set of shapes) and a growing list of operations you want to run over them (render, export, validate, compute a cost). Putting every operation directly on every node class means editing all of them every single time you add one.

Python
from abc import ABC, abstractmethod

class Node(ABC):
    @abstractmethod
    def accept(self, visitor: "Visitor"): ...

class Number(Node):
    def __init__(self, value: int): self.value = value
    def accept(self, visitor): return visitor.visit_number(self)

class Add(Node):
    def __init__(self, left: Node, right: Node): self.left, self.right = left, right
    def accept(self, visitor): return visitor.visit_add(self)

class Multiply(Node):
    def __init__(self, left: Node, right: Node): self.left, self.right = left, right
    def accept(self, visitor): return visitor.visit_multiply(self)

class Visitor(ABC):
    @abstractmethod
    def visit_number(self, n: Number): ...
    @abstractmethod
    def visit_add(self, n: Add): ...
    @abstractmethod
    def visit_multiply(self, n: Multiply): ...

class Evaluate(Visitor):                       # operation 1
    def visit_number(self, n): return n.value
    def visit_add(self, n): return n.left.accept(self) + n.right.accept(self)
    def visit_multiply(self, n): return n.left.accept(self) * n.right.accept(self)

class PrettyPrint(Visitor):                    # operation 2 — not one Node class was touched
    def visit_number(self, n): return str(n.value)
    def visit_add(self, n): return f"({n.left.accept(self)} + {n.right.accept(self)})"
    def visit_multiply(self, n): return f"({n.left.accept(self)} * {n.right.accept(self)})"


tree = Multiply(Add(Number(2), Number(3)), Number(4))     # (2 + 3) * 4
assert tree.accept(Evaluate()) == 20
assert tree.accept(PrettyPrint()) == "((2 + 3) * 4)"
print(tree.accept(PrettyPrint()), "=", tree.accept(Evaluate()))

The trade-off here runs exactly backwards from normal polymorphism, and that's the insight to state out loud: adding a new operation is free, just write a new visitor. Adding a new node type means editing every single existing visitor. So Visitor is the right call when the type hierarchy is stable and operations keep piling up, like in compilers, document processors, and static analysers, and the wrong call when new types show up often.

Interpreter — the idea: represent a grammar as classes, and evaluate sentences written in it. The Evaluate visitor above is really a tiny interpreter for a small arithmetic grammar. In real systems you'll meet this in rule engines, query filters, and search-query languages. For anything bigger than that, a real parser generator beats hand-written interpreter classes — it's enough to recognise "this is a parsing problem, not a pattern problem" and say so.

Remember: Visitor flips the usual trade-off — new operations are free, new types are expensive. Know which way your problem actually leans before reaching for it.

Sign in to try this knowledge check.

Anti-patterns: recognising bad design out loud

Being able to name a smell and its fix live, mid-interview, is worth just as much as building a clean design in the first place — because that's exactly what a real design review sounds like.

Anti-pattern What it looks like Why it's a problem The fix
God object OrderManager with 40 methods and 15 fields Every change has to touch it; nothing can be tested on its own Split it up by why each part would change (SRP)
Anaemic domain model Entities that are just getters and setters; all the real logic lives in *Service classes The data and the rules about that data live apart, so invalid states leak through everywhere Move behaviour onto the entity that actually owns the data
Spaghetti inheritance Five-level class trees; subclasses overriding methods just to disable them Fragile base classes; breaks Liskov Substitution Composition instead; small, focused interfaces
Primitive obsession A plain string standing in for money, an id, a phone number, a currency code The type system can't stop you mixing them up; checks end up scattered everywhere Value objects: Money, TicketId
Magic strings and numbers if status == 3, if role == "adm" A typo still compiles fine; the meaning is invisible Enums and named constants
Feature envy A method that mostly reads fields from a different class Coupling to a shape you don't own Move the method to the class that owns the data
Circular dependency Order imports Customer, which imports Order Neither one can be understood or tested alone Extract an interface, or invert the dependency
Leaky abstraction A Repository that hands back a raw ORM query object Callers end up depending on the storage technology anyway Return plain domain objects only
Boolean trap save(true, false, true) Completely unreadable at the call site Named arguments, enums, or separate methods
Copy-paste inheritance Subclassing purely to reuse one method Ties two unrelated things together permanently Pull the shared logic into its own collaborator
Pattern fever An interface, a factory, and a strategy for one concrete class that will never have a second All the extra layers, none of the payoff Delete it. Add the abstraction the day a second case actually shows up

That last row deserves special attention, because after a whole pattern catalogue it's tempting to use every one of them: the single most common mistake after learning these patterns is reaching for one where a plain class would have done fine. An interface with one implementation and no realistic second one is a cost with no benefit. Often the strongest possible answer is: "I'd keep this concrete for now. If a second pricing rule ever shows up, this is exactly where the strategy would go." That shows you know both the pattern and its price.

Remember: the entity that owns the data should own the rules about it too. Data and logic living apart is an anaemic domain model, and it's the most common structural mistake in real codebases.

Sign in to try this knowledge check.

Quick recap

What question triggers which pattern:

The question you're being asked The pattern
"How do I add a new type without editing every caller?" Factory / Strategy
"How do I add behaviour without subclassing?" Decorator
"How do I make several things react to one change?" Observer
"How do I support undo?" Command (plus Memento)
"How do I stop illegal state transitions?" State
"How do I run a request through configurable steps?" Chain of Responsibility
"How do I treat one thing and a group of them the same way?" Composite
"How do I fit a third-party API to my own interface?" Adapter
"How do I hide a messy subsystem?" Facade
"How do I control access to an object?" Proxy
"How do I add operations to a fixed set of types?" Visitor
"How do I cut down n-to-n communication?" Mediator
"How do I share state across millions of objects?" Flyweight
"How do I safely build a complicated object?" Builder
"How do I guarantee exactly one instance exists?" Singleton (and consider injecting it instead)
  • Chain of Responsibility is the single most valuable pattern in this lesson — every middleware stack, logging library, and approval workflow is one, and you'll meet it directly in the rate-limiter and logging-framework designs later in this section.
  • Visitor's trade-off runs backwards from usual polymorphism: cheap new operations, expensive new types. Say which way your domain actually leans.
  • The anti-pattern table is an interview tool in itself. When someone asks "what's wrong with this design?", you want names and fixes ready, not just a vague sense that something feels off.
  • Know when not to reach for a pattern. After a catalogue this long, restraint is what separates a strong answer from a weak one — abstract exactly where things actually vary, and say out loud when you're deliberately choosing to keep something simple.
PreviousBehavioral Patterns I — Strategy, Observer, Command, State, Template MethodNextConcurrency in Low-Level Design

Course progress

0%
0 of 177 modules complete

On this page

  • Chain of Responsibility
  • Iterator, Mediator, and Memento
  • Visitor and Interpreter
  • Anti-patterns: recognising bad design out loud
  • Quick recap