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
Creational 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

Creational Patterns

Reading45 min

Every creational pattern answers the same question: who decides which concrete class gets built, and how? They all exist for one reason — to stop new ConcreteThing() from being scattered through code that has no business knowing ConcreteThing even exists.

Learn each one as a problem → pattern → cost triple. Interviewers ask "when would you reach for this?" far more often than "implement this from scratch," and the trap answer is using a pattern where a plain constructor would have done the job just fine.

Singleton — exactly one instance

The problem: something expensive or genuinely one-of-a-kind — a connection pool, a config store, a logger — needs to exist exactly once, shared by everyone.

Python
import threading

class ConfigRegistry:
    _instance = None
    _lock = threading.Lock()

    def __new__(cls):
        if cls._instance is None:                 # fast path, no lock needed most of the time
            with cls._lock:                       # double-checked locking
                if cls._instance is None:         # check again, now that we hold the lock
                    obj = super().__new__(cls)
                    obj._values = {}
                    cls._instance = obj
        return cls._instance

    def set(self, key: str, value: str) -> None: self._values[key] = value
    def get(self, key: str) -> str | None: return self._values.get(key)


a, b = ConfigRegistry(), ConfigRegistry()
assert a is b                       # truly the same object
a.set("region", "ap-south-1")
assert b.get("region") == "ap-south-1"
print("singleton shared state:", b.get("region"))

That double-checked lock isn't decoration — interviewers ask about it directly. Without it, two threads can both pass the first is None check before either one finishes building the object, and you end up with two "singletons." Locking on every access instead would work, but you'd pay that cost forever, on every single call. Check, lock, check again solves both problems at once.

In Python, a module is already a singleton by nature — import it twice and you get the same object both times — so a plain module-level variable is usually the natural answer. In Java, the safest version is an enum-based singleton (it resists both serialization tricks and reflection) or a static holder class.

Here's the cost, and you should mention it before anyone asks, because "when would you not use this?" is always the follow-up:

  • It's shared mutable state: any code, anywhere, can change it, so bugs in it have no clear owner.
  • It hides what a class actually depends on: a class quietly calling ConfigRegistry() internally looks dependency-free from the outside, but isn't.
  • It makes testing painful: state can leak between tests, and there's no way to swap in a fake.
  • It fights concurrency: shared mutable state needs protecting everywhere it's touched.

The modern answer: build one instance where your program starts up, and inject it into whatever needs it. You keep "exactly one," without the global door that let anything reach in and change it — which was always the harmful half of this pattern anyway.

Remember: the useful part of Singleton is "exactly one instance." The harmful part is "reachable from anywhere." Keep the first, inject instead of relying on the second.

Sign in to try this knowledge check.

Factory Method and Abstract Factory

Factory Method — the problem: callers shouldn't need to know or name concrete classes, and adding a new type shouldn't mean editing them.

Python
from abc import ABC, abstractmethod

class Notifier(ABC):
    @abstractmethod
    def send(self, to: str, msg: str) -> str: ...

class EmailNotifier(Notifier):
    def send(self, to: str, msg: str) -> str: return f"email->{to}:{msg}"

class SmsNotifier(Notifier):
    def send(self, to: str, msg: str) -> str: return f"sms->{to}:{msg}"

class PushNotifier(Notifier):
    def send(self, to: str, msg: str) -> str: return f"push->{to}:{msg}"

class NotifierFactory:
    """A registry: a new channel just registers itself, nothing here has to change."""
    _registry: dict[str, type[Notifier]] = {}

    @classmethod
    def register(cls, key: str, impl: type[Notifier]) -> None:
        cls._registry[key] = impl

    @classmethod
    def create(cls, key: str) -> Notifier:
        try:
            return cls._registry[key]()
        except KeyError:
            raise ValueError(f"unknown channel: {key}") from None


for key, impl in (("email", EmailNotifier), ("sms", SmsNotifier), ("push", PushNotifier)):
    NotifierFactory.register(key, impl)

assert NotifierFactory.create("sms").send("+91999", "hi") == "sms->+91999:hi"
try:
    NotifierFactory.create("carrier-pigeon")
    raise AssertionError("expected failure")
except ValueError as e:
    print("factory rejected:", e)

This registry version is worth knowing because it's actually open/closed: a plain if kind == "email" factory still needs editing for every new channel. This one doesn't — a new channel just registers itself.

Abstract Factory — the problem: you need whole families of related objects that must always be used together, where mixing pieces from different families would be a genuine bug.

Python
from abc import ABC, abstractmethod

class Button(ABC):
    @abstractmethod
    def render(self) -> str: ...

class Checkbox(ABC):
    @abstractmethod
    def render(self) -> str: ...

class DarkButton(Button):
    def render(self) -> str: return "[dark button]"

class DarkCheckbox(Checkbox):
    def render(self) -> str: return "[dark checkbox]"

class LightButton(Button):
    def render(self) -> str: return "[light button]"

class LightCheckbox(Checkbox):
    def render(self) -> str: return "[light checkbox]"

class ThemeFactory(ABC):                    # the abstract factory
    @abstractmethod
    def button(self) -> Button: ...
    @abstractmethod
    def checkbox(self) -> Checkbox: ...

class DarkTheme(ThemeFactory):
    def button(self) -> Button: return DarkButton()
    def checkbox(self) -> Checkbox: return DarkCheckbox()

class LightTheme(ThemeFactory):
    def button(self) -> Button: return LightButton()
    def checkbox(self) -> Checkbox: return LightCheckbox()

def render_form(theme: ThemeFactory) -> str:
    # There's no way to accidentally end up with a dark button next to a light checkbox.
    return theme.button().render() + theme.checkbox().render()


assert render_form(DarkTheme()) == "[dark button][dark checkbox]"
print(render_form(LightTheme()))

Here's the one-line difference to remember: Factory Method builds one product, varying by subclass or a key. Abstract Factory builds a whole matching family at once, and guarantees the pieces actually go together.

Remember: if mixing two "compatible" objects from different sources would be a bug, that's Abstract Factory's job. If you're just building one type of thing, a plain factory is enough.

Sign in to try this knowledge check.

Builder — put together a complex object step by step

The problem: an object has a pile of optional fields, and the constructor has grown into Pizza(size, cheese, sauce, toppings, crust, extra, discount), where half the arguments are usually None and nobody remembers what order they go in. This is sometimes called the "telescoping constructor" problem.

Python
from dataclasses import dataclass, field

@dataclass(frozen=True)
class HttpRequest:
    url: str
    method: str = "GET"
    headers: dict[str, str] = field(default_factory=dict)
    body: str | None = None
    timeout_ms: int = 5_000

class HttpRequestBuilder:
    def __init__(self, url: str):
        self._url, self._method = url, "GET"
        self._headers: dict[str, str] = {}
        self._body: str | None = None
        self._timeout = 5_000

    def method(self, m: str) -> "HttpRequestBuilder":
        self._method = m
        return self                                  # returning self is what makes chaining work

    def header(self, k: str, v: str) -> "HttpRequestBuilder":
        self._headers[k] = v
        return self

    def body(self, b: str) -> "HttpRequestBuilder":
        self._body = b
        return self

    def timeout_ms(self, ms: int) -> "HttpRequestBuilder":
        self._timeout = ms
        return self

    def build(self) -> HttpRequest:
        # Checks live here, so a broken object can never be created in the first place.
        if self._method in ("POST", "PUT") and self._body is None:
            raise ValueError(f"{self._method} requires a body")
        return HttpRequest(self._url, self._method, dict(self._headers),
                           self._body, self._timeout)


req = (HttpRequestBuilder("https://api.mindforge.test/orders")
       .method("POST")
       .header("Content-Type", "application/json")
       .body('{"total":49900}')
       .timeout_ms(2_000)
       .build())

assert req.method == "POST" and req.timeout_ms == 2_000
try:
    HttpRequestBuilder("https://x").method("POST").build()
    raise AssertionError("expected validation failure")
except ValueError as e:
    print("builder validated:", e)

Three things make Builder worth the extra typing: the call site reads like a sentence ("method POST, header this, body that"), checks all happen at build(), so a half-finished object is never observable, and the finished object never changes afterward, so it's safe to hand to any thread.

When you don't need it: any language with named arguments and defaults — Python, Kotlin, C# — already solves the readability half of this problem. Reach for a builder there specifically when you need the validation and the unchangeability, or when the object gets assembled in pieces across different parts of your code.

Remember: Builder buys you three things beyond named arguments — validation at one point, an object that never changes, and the ability to build it in stages.

Sign in to try this knowledge check.

Prototype and Object Pool

Prototype — the problem: building an object from scratch is expensive (a heavy parse, a network fetch, a big computation), and you need lots of near-identical copies.

Python
import copy

class DocumentTemplate:
    def __init__(self, sections: list[str], styles: dict[str, str]):
        self.sections = sections            # imagine this took a while to build
        self.styles = styles

    def clone(self) -> "DocumentTemplate":
        return copy.deepcopy(self)          # deep, so the copy doesn't secretly share state


base = DocumentTemplate(["cover", "body"], {"font": "serif"})
invoice = base.clone()
invoice.sections.append("totals")
invoice.styles["font"] = "mono"

assert base.sections == ["cover", "body"]        # the original is untouched
assert base.styles["font"] == "serif"
print("prototype cloned independently:", invoice.sections, invoice.styles)

The whole reason this pattern exists is to warn you about shallow vs. deep copies. A shallow copy shares the nested list underneath, so changing the clone silently corrupts the original too. Interviewers ask this directly — know that copy.copy is shallow and copy.deepcopy is deep in Python, and that Java's clone() is shallow by default (most people consider Cloneable a design mistake; a copy constructor or a static factory method is usually preferred instead).

Object Pool — the problem: objects are expensive to both create and destroy, and you need a steady supply of them over time — database connections, threads, large buffers.

Python
class Connection:
    def __init__(self, cid: int): self.cid, self.in_use = cid, False
    def query(self, sql: str) -> str: return f"conn{self.cid}:{sql}"

class ConnectionPool:
    def __init__(self, size: int):
        self._pool = [Connection(i) for i in range(size)]   # all built up front, once

    def acquire(self) -> Connection:
        for c in self._pool:
            if not c.in_use:
                c.in_use = True
                return c
        raise RuntimeError("pool exhausted")     # a deliberate limit — this is backpressure

    def release(self, c: Connection) -> None:
        c.in_use = False                          # reset before it's handed to anyone else


pool = ConnectionPool(2)
a, b = pool.acquire(), pool.acquire()
try:
    pool.acquire()
    raise AssertionError("pool should be exhausted")
except RuntimeError:
    pass
pool.release(a)
c = pool.acquire()                                # 'a' gets reused
assert c is a
print("pooled:", c.query("SELECT 1"))

Two things worth saying about pools in an interview: the pool has to be bounded (an unbounded pool is just uncontrolled growth in disguise, and that "pool exhausted" error is doing its job on purpose), and state must be reset when something is returned, or the next borrower inherits whatever the last one left behind — an open transaction, a stale timeout, leftover session data. That exact reset bug is the classic production incident this pattern tends to cause.

Remember: a pool without a bound isn't a pool, it's just delayed uncontrolled growth. Reset state on release, every time.

Sign in to try this knowledge check.

Quick recap

Problem, pattern, cost:

Pattern Problem it solves The cost, or when to skip it
Singleton Exactly one shared instance of something expensive or unique Global mutable state, hidden dependencies, hard to test — inject one instance instead
Factory Method Callers shouldn't name concrete classes; new types shouldn't touch callers Extra indirection; needs a registry to be truly open/closed
Abstract Factory A whole family of products that must match Adding a new product type means editing every factory
Builder Lots of optional fields, checks, and an unchangeable result Extra typing; keyword arguments already cover the simple case
Prototype Copying is much cheaper than building from scratch Shallow-vs-deep copy bugs
Object Pool Building and destroying are both expensive; reuse is safe Must be bounded; must reset state on release

How to use all of this in an interview:

  • Say the problem before you say the pattern name. "Payment methods will keep getting added, so I'll build them through a registry-backed factory" is design. "I'll use the Factory pattern" on its own is just vocabulary.
  • Bring up the cost yourself, before anyone asks. Every pattern above has a well-known downside, and naming it unprompted is what makes you sound like someone who's actually maintained this kind of code, not just read about it.
  • The single most common mistake is reaching for Singleton everywhere. Use it only for things that are genuinely one-of-a-kind, prefer injecting a single instance instead of a global one, and be ready to critique it — that critique is frequently the real question being asked.
  • A plain constructor is often the right answer. Creational patterns earn their place when building something varies, costs a lot, or needs validating — not as a default habit.
PreviousSOLID PrinciplesNextStructural Patterns

Course progress

0%
0 of 177 modules complete

On this page

  • Singleton — exactly one instance
  • Factory Method and Abstract Factory
  • Builder — put together a complex object step by step
  • Prototype and Object Pool
  • Quick recap