Design Patterns · with real-world uses · Field GuideDesign Patterns · खऱ्या-जगातील वापरांसह · Field Guide

Design Patterns Pro Playbook

Design Patterns — प्रो प्लेबुक

The Gang of Four patterns plus the modern ones, taught through systems you actually useReact.createElement, OkHttp builders, Express middleware, HikariCP pools, Kafka, circuit breakers — not Shape, Animal, and Pizza. Each with the problem it solves, real code, and when not to reach for it.

Gang of Four चे patterns आणि modern patterns, शिकवले तुम्ही प्रत्यक्ष वापरता त्या systems मधून — React.createElement, OkHttp builders, Express middleware, HikariCP pools, circuit breakers — ना Shape, Animal, Pizza. प्रत्येक तो सोडवणारी समस्या, खरा code, आणि कधी घेऊ नये यासह.

Every card ends with a "🌍 In the wild" note — the real library or system that IS this pattern
प्रत्येक card "🌍 प्रत्यक्ष वापरात" टीपेने संपतं — जो खरा library किंवा system म्हणजेच हा pattern
What it is हे काय आहे Why it matters का महत्त्वाचं How it works कसं चालतं In the wild प्रत्यक्ष वापरात ASCII diagram ASCII आकृती
Part I · Creational — how objects get made
भाग I · Creational — objects कसे बनतात
01Why design patterns — shared vocabulary for recurring problemsDesign patterns का — पुनरावृत्त समस्यांसाठी सामायिक शब्दसंग्रह★ start here★ इथून सुरू करा

Scenario: in a review you say "let's inject a strategy here and wrap the client in a decorator with a circuit breaker" — and the whole team instantly knows the shape of the solution. That's the real value of patterns: names for solutions that recur, so you design and communicate faster and recognize structures already proven in the wild.

परिस्थिती: review मध्ये तुम्ही म्हणता 'इथे एक strategy inject करू आणि client ला circuit breaker असलेल्या decorator मध्ये wrap करू' — आणि संपूर्ण team ला उपायाचा आकार लगेच कळतो. patterns चं खरं मूल्य हेच: पुनरावृत्त होणाऱ्या उपायांना नावं, म्हणजे तुम्ही जलद design आणि संवाद करता आणि सिद्ध झालेल्या रचना ओळखता.

What
काय

Design patterns are named, reusable solutions to problems that keep recurring in software design — a shared vocabulary (from the 1994 "Gang of Four" book and beyond), not copy-paste code.

Design patterns म्हणजे software design मध्ये पुन्हा पुन्हा येणाऱ्या समस्यांचे नावाचे, पुनर्वापर करण्याजोगे उपाय — एक सामायिक शब्दसंग्रह (1994 च्या 'Gang of Four' पुस्तकातून आणि पुढे), copy-paste code नाही.

Why
का

They compress communication ("it's an adapter"), encode hard-won tradeoffs, and help you recognize structures you already see in frameworks — so you don't reinvent (or misuse) them.

ते संवाद संकुचित करतात ('हे adapter आहे'), कष्टाने मिळवलेल्या तडजोडी encode करतात, आणि frameworks मध्ये तुम्ही आधीच पाहता त्या रचना ओळखायला मदत करतात — म्हणून तुम्ही त्या पुन्हा-शोधत (किंवा गैरवापर करत) नाही.

How
कसं

Learn the problem each solves, not just its diagram. Reach for one when you hit its problem — never apply a pattern for its own sake (topic 28).

प्रत्येक कोणती समस्या सोडवतो ते शिका, फक्त आकृती नाही. त्याची समस्या भेटेल तेव्हा एक घ्या — कधीही pattern-साठी-pattern लावू नका (topic 28).

Three families (plus modern architectural ones)
तीन कुटुंबं (आणि modern architectural)
   CREATIONAL   how objects are CREATED     Factory, Builder, Singleton, Pool…
   STRUCTURAL   how objects are COMPOSED    Adapter, Decorator, Facade, Proxy…
   BEHAVIORAL   how objects INTERACT        Strategy, Observer, Command, State…
   MODERN       app/infra architecture      Repository, Pub/Sub, Circuit Breaker
   a pattern = (recurring problem) → (proven solution) → (its tradeoffs)
🧠
Patterns are chess openings: "Sicilian Defense" names a whole plan both players recognize. You don't play an opening to show off — you play it because the position calls for it. Same with patterns: the situation calls for a Strategy, so you name and use one.
🧠
Patterns म्हणजे chess openings: 'Sicilian Defense' एका संपूर्ण योजनेला नाव देतं जी दोन्ही खेळाडू ओळखतात. तुम्ही दाखवायला opening खेळत नाही — position मागते म्हणून खेळता. Patterns तसंच: परिस्थिती Strategy मागते, म्हणून तुम्ही एक नाव देऊन वापरता.
📚
Framework docs already speak patterns: React "render props," Spring "template," Node "streams/observer," Express "middleware chain." Learning patterns is learning to read the frameworks you already depend on.
📚
Framework docs आधीच patterns बोलतात: React 'render props', Spring 'template', Node 'streams/observer', Express 'middleware chain'. Patterns शिकणं म्हणजे तुम्ही आधीच अवलंबून असलेली frameworks वाचायला शिकणं.
Same idea, one shared word
// without vocabulary — a long explanation:
"make an object that stands in for the real client, and around it wrap
 something that adds retry, and something that trips after N failures…"

// with vocabulary — one sentence a reviewer parses instantly:
const client = new CircuitBreaker(new RetryDecorator(new HttpClient(), 3))
//               ^ pattern 27          ^ pattern 09 (decorator)   ^ the real thing
// the NAMES carry the design. that's what patterns buy you.
🧒
In plain wordsDesign patterns are just named recipes for problems programmers keep bumping into. Once everyone knows the recipe names, you can say "let's use the such-and-such recipe" and skip the long explanation. The point isn't to use fancy recipes everywhere — it's to recognize when you're facing a problem someone already solved well, and reuse that solution.
🧒
सोप्या शब्दांतDesign patterns म्हणजे programmers ला वारंवार भेटणाऱ्या समस्यांसाठी फक्त नावाच्या पाककृती. एकदा सगळ्यांना पाककृतींची नावं माहीत झाली की तुम्ही 'अमुक पाककृती वापरू' म्हणून लांब स्पष्टीकरण टाळता. मुद्दा सगळीकडे fancy पाककृती वापरणं नाही — कुणीतरी आधीच नीट सोडवलेल्या समस्येला तुम्ही भेटताय हे ओळखणं आणि तो उपाय पुनर्वापरणं.
🌍
In the wildThe GoF book Design Patterns (1994) named 23 of these; frameworks are built from them — React (composition, HOCs/decorators, render props), Spring (template method, proxy, factory), Node streams (observer/iterator), Express/Rack (chain of responsibility). Cloud resilience libraries (Hystrix, Polly, resilience4j) ship circuit breakers and retries as named building blocks.
🌍
प्रत्यक्ष वापरातGoF पुस्तक Design Patterns (1994) यांतले 23 ना नाव दिलं; frameworks यांपासून बांधलेली आहेत — React (composition, HOCs/decorators, render props), Spring (template method, proxy, factory), Node streams (observer/iterator), Express/Rack (chain of responsibility). Cloud resilience libraries (Hystrix, Polly, resilience4j) circuit breakers आणि retries नावाचे building blocks म्हणून देतात.

✅ Do

  • Learn the problem each pattern solves and its tradeoffs
  • Use patterns to communicate design intent quickly
  • Recognize patterns already in your frameworks and lean on them

✅ हे करा

  • प्रत्येक pattern कोणती समस्या सोडवतो आणि त्याच्या तडजोडी शिका
  • design हेतू जलद संवादायला patterns वापरा
  • तुमच्या frameworks मध्ये आधीच असलेले patterns ओळखा आणि त्यांवर झुका

❌ Don't

  • Apply a pattern because it's clever — apply it because you have its problem
  • Treat the GoF diagrams as mandatory boilerplate to reproduce verbatim

❌ हे टाळा

  • pattern हुशार आहे म्हणून लावू नका — त्याची समस्या आहे म्हणून लावा
  • GoF आकृत्या हुबेहूब पुन्हा बनवायच्या अनिवार्य boilerplate समजू नका
Gotcha: patterns are a vocabulary, not a checklist. The classic failure (topic 28) is "pattern fever" — forcing Singletons, Factories, and Visitors onto code that a plain function or a language feature would handle better. Modern languages absorbed several GoF patterns as built-ins (iterators, first-class functions replacing Strategy/Command, Optional replacing Null Object). Know them to recognize and communicate — reach for them only when the recurring problem actually shows up.
अडचण: Patterns हा शब्दसंग्रह आहे, checklist नाही. Classic अपयश (topic 28) म्हणजे 'pattern-ताप' — साधं function किंवा language-feature चांगलं हाताळेल अशा code वर Singletons, Factories, आणि Visitors लादणं. Modern languages नी अनेक GoF patterns built-in म्हणून सामावले (iterators, Strategy/Command बदलणारी first-class functions, Null Object बदलणारं Optional). ओळखायला आणि संवादायला शिका — समस्या खरंच आली तरच एक घ्या.
02Factory Method — decide the concrete type in one placeFactory Method — concrete type एका ठिकाणी ठरवा★ core★ गाभा

Scenario: your code needs a Logger, but which one — console in dev, JSON-to-stdout in prod, a no-op in tests — depends on config. Scattering new JsonLogger() everywhere hard-codes the choice. A factory method centralizes "which concrete class to build" behind a single call: Logger.create(env).

परिस्थिती: तुमच्या code ला Logger हवा, पण कोणता — dev मध्ये console, prod मध्ये JSON, tests मध्ये no-op — config वर अवलंबून. सगळीकडे new JsonLogger() विखुरणं निवड hardcode करतं. एक factory method 'कोणती concrete class बांधायची' हे एका call मागे केंद्रित करतं: Logger.create(env).

What
काय

Factory Method: a method whose job is to create and return an object, deciding the concrete type internally. Callers ask for the abstraction and get back a ready-made instance.

Factory Method: एक method जिचं काम object बनवून परत करणं, concrete type आत ठरवणं. Callers abstraction मागतात आणि तयार instance मिळतो.

Why
का

It puts the "which class + how to build it" decision in one place, so callers stay decoupled from concrete constructors and construction logic isn't duplicated.

ते 'कोणती class + कशी बांधायची' निर्णय एका ठिकाणी ठेवतं, म्हणून callers concrete constructors पासून decoupled राहतात आणि construction logic नक्कल होत नाही.

How
कसं

Replace scattered new X() with a factory function/method that returns the interface type and picks the implementation (by config, input, or platform).

विखुरलेले new X() एका factory function/method ने बदला जी interface type परत करते आणि implementation निवडते (config, input, किंवा platform नुसार).

One place decides the concrete type
एक जागा concrete type ठरवते
   callers:  const log = makeLogger(env)     ← ask for a Logger
                          │
                   ┌──────┴───────┐  factory decides:
              env==='prod'?   env==='test'?
                   │               │
              JsonLogger       NoopLogger      (else) ConsoleLogger
   change the selection rule ONCE, in the factory — not at every call site
🧠
React.createElement is a factory: JSX compiles to React.createElement(type, props), which builds the right element object for you — you never new a fiber. document.createElement('div') is the DOM's factory doing the same.
🧠
React.createElement म्हणजे factory: JSX React.createElement(type, props) मध्ये compile होतं, जे योग्य element object तुमच्यासाठी बांधतं — तुम्ही fiber कधीच new करत नाही. document.createElement('div') म्हणजे DOM चा factory तेच करतो.
🏭
Config-driven construction: logging.getLogger() (Python), a DriverManager.getConnection(url) that returns the right JDBC driver, and DocumentBuilderFactory all hide "which concrete class + how to build it" behind one call.
🏭
Config-चालित construction: logging.getLogger() (Python), योग्य JDBC driver परत करणारा DriverManager.getConnection(url), आणि DocumentBuilderFactory सगळे 'कोणती concrete class + कशी बांधायची' एका call मागे लपवतात.
A factory picks the implementation by config
interface Logger { info(msg: string): void; error(msg: string): void }
class ConsoleLogger implements Logger { /* ... */ }
class JsonLogger    implements Logger { /* ... */ }
class NoopLogger    implements Logger { info(){} error(){} }

function makeLogger(env: string): Logger {         // the factory method
  if (env === 'production') return new JsonLogger()
  if (env === 'test')       return new NoopLogger()
  return new ConsoleLogger()
}
// callers: const log = makeLogger(process.env.NODE_ENV)  — no `new` at call sites
🧒
In plain wordsInstead of every part of your code knowing how to build the exact thing it needs, they all ask one helper: "give me a logger." The helper figures out which kind to make based on the situation and hands it over. If you later change how loggers are chosen, you fix the one helper — not a hundred places that each said "build a JSON logger."
🧒
सोप्या शब्दांततुमच्या code च्या प्रत्येक भागाला त्याला लागणारी नेमकी गोष्ट कशी बांधायची हे माहीत असण्याऐवजी, ते सगळे एका helper ला विचारतात: 'मला logger दे.' Helper परिस्थितीनुसार कोणता बनवायचा ते ठरवतो आणि देतो. नंतर loggers कसे निवडले जातात ते बदललं, तर तुम्ही एक helper दुरुस्त करता — 'JSON logger बांध' म्हणणाऱ्या शंभर जागा नाही.
🌍
In the wildReact.createElement / document.createElement, Python logging.getLogger, Java DocumentBuilderFactory / Calendar.getInstance() / DriverManager.getConnection, AWS SDK S3Client.builder().build(), and ORM createConnection(config). Any createX() / makeX() / getX() that hides which concrete class you get is a factory.
🌍
प्रत्यक्ष वापरातReact.createElement / document.createElement, Python logging.getLogger, Java DocumentBuilderFactory / Calendar.getInstance() / DriverManager.getConnection, AWS SDK S3Client.builder().build(), आणि ORM createConnection(config). कुठलंही createX() / makeX() / getX() जे तुम्हाला कोणती concrete class मिळते ते लपवतं तो factory.

✅ Do

  • Centralize "which implementation + how to build it" in a factory
  • Return the interface type so callers stay decoupled
  • Use it when construction depends on config, input, or platform

✅ हे करा

  • 'कोणती implementation + कशी बांधायची' factory मध्ये केंद्रित करा
  • callers decoupled ठेवायला interface type परत करा
  • construction config, input, किंवा platform वर अवलंबून असेल तेव्हा वापरा

❌ Don't

  • Wrap a single trivial new in a factory for no reason
  • Let the factory grow into a God object that builds unrelated things

❌ हे टाळा

  • एका क्षुल्लक new ला विनाकारण factory मध्ये गुंडाळू नका
  • factory ला असंबंधित गोष्टी बांधणारा God object होऊ देऊ नका
Gotcha: in languages with first-class functions, a factory is often just a function — you rarely need the full GoF "creator subclass overrides factoryMethod()" hierarchy. And don't confuse Factory with Dependency Injection: a factory creates objects on demand (you call it when you need one); DI supplies already-created collaborators (you receive them). They compose — a DI container often uses factories internally — but reaching for a factory when you actually just want to inject a dependency adds a needless layer.
अडचण: first-class functions असलेल्या languages मध्ये factory अनेकदा फक्त एक function असतो — तुम्हाला पूर्ण GoF hierarchy क्वचितच लागते. आणि Factory ला Dependency Injection शी गोंधळू नका: factory गरजेनुसार objects बनवतो (हवं तेव्हा call करता); DI आधीच-बनवलेले collaborators पुरवतं (तुम्ही घेता). ते compose होतात, पण फक्त dependency inject करायची असताना factory घेणं अनावश्यक स्तर जोडतं.
03Abstract Factory — families of related objectsAbstract Factory — संबंधित objects ची कुटुंबं

Scenario: your app targets AWS and GCP. On AWS you need an S3 blob store, SQS queue, and CloudWatch metrics; on GCP, GCS, Pub/Sub, and Stackdriver. You must never mix an S3 store with a Pub/Sub queue. An abstract factory hands you a whole compatible family at once: cloud.blobStore(), cloud.queue(), cloud.metrics() — all AWS, or all GCP.

परिस्थिती: तुमचा app AWS आणि GCP लक्ष्य करतो. AWS वर S3 blob store, SQS queue, CloudWatch metrics लागतात; GCP वर GCS, Pub/Sub, Stackdriver. तुम्ही S3 store ला Pub/Sub queue सोबत कधीच मिसळू नये. एक abstract factory तुम्हाला संपूर्ण सुसंगत कुटुंब एकाच वेळी देतो: सगळे AWS, किंवा सगळे GCP.

What
काय

Abstract Factory: an interface for creating a family of related objects without specifying their concrete classes, so the whole set stays consistent.

Abstract Factory: संबंधित objects च्या कुटुंबाला त्यांच्या concrete classes न सांगता बनवायचं interface, म्हणून संपूर्ण संच सुसंगत राहतो.

Why
का

It guarantees the pieces you get work together (all AWS, or all GCP, or all "dark theme") and lets you swap the entire family by swapping one factory.

ते तुम्हाला मिळणारे तुकडे एकत्र चालतात याची हमी देतं (सगळे AWS, किंवा सगळे GCP) आणि एक factory बदलून संपूर्ण कुटुंब बदलू देतं.

How
कसं

Define a factory interface with several create-methods; implement one factory per family; inject the chosen factory and build all pieces through it.

अनेक create-methods असलेलं factory interface ठरवा; प्रति कुटुंब एक factory राबवा; निवडलेला factory inject करा आणि सगळे तुकडे त्यातून बांधा.

Pick the family once; every piece matches
कुटुंब एकदा निवडा; प्रत्येक तुकडा जुळतो
   interface CloudFactory { blobStore()  queue()  metrics() }
        ┌───────────────────────┴───────────────────────┐
   AwsFactory                                        GcpFactory
     blobStore → S3        queue → SQS                blobStore → GCS
     metrics   → CloudWatch                           queue → PubSub, metrics → Stackdriver
   inject AwsFactory ⇒ you CAN'T accidentally get an S3 store with a PubSub queue
🧠
Cross-platform UI toolkits are the textbook case: a WidgetFactory that makes a matching Button, Checkbox, and Menu for macOS or Windows — so you never render a Windows button next to a macOS checkbox. Same shape as "all-AWS vs all-GCP."
🧠
Cross-platform UI toolkits हे textbook उदाहरण: एक WidgetFactory जो macOS किंवा Windows साठी जुळणारा Button, Checkbox, Menu बनवतो — म्हणून तुम्ही Windows button macOS checkbox शेजारी कधीच render करत नाही.
🎨
Theme providers: a ThemeFactory yields a consistent set of colors, spacings, and components for "dark" or "light." One switch changes the entire coordinated family, not thirty individual choices.
🎨
Theme providers: एक ThemeFactory 'dark' किंवा 'light' साठी सुसंगत रंग, spacings, आणि components चा संच देतो. एक switch संपूर्ण समन्वित कुटुंब बदलतो, तीस स्वतंत्र निवडी नाही.
A factory per cloud; the family stays consistent
interface CloudFactory {
  blobStore(): BlobStore
  queue(): Queue
  metrics(): Metrics
}
class AwsFactory implements CloudFactory {
  blobStore(){ return new S3Store() }
  queue(){ return new SqsQueue() }
  metrics(){ return new CloudWatch() }
}
class GcpFactory implements CloudFactory {
  blobStore(){ return new GcsStore() }
  queue(){ return new PubSubQueue() }
  metrics(){ return new Stackdriver() }
}
// wire once: const cloud: CloudFactory = onAws ? new AwsFactory() : new GcpFactory()
// everything built from `cloud` is guaranteed same-family
🧒
In plain wordsSometimes you need a matching set of things — like a whole outfit where the shoes, belt, and bag all go together. An abstract factory is the shop assistant who, once you pick "the summer collection," hands you pieces that all match. You can't accidentally end up with winter boots and a summer hat, because everything came from the same coordinated set.
🧒
सोप्या शब्दांतकधीकधी तुम्हाला गोष्टींचा जुळणारा संच हवा असतो — जसं संपूर्ण outfit जिथे बूट, belt, आणि bag सगळे एकत्र जातात. Abstract factory म्हणजे तो दुकानदार जो, तुम्ही 'summer collection' निवडताच, सगळे जुळणारे तुकडे देतो. तुम्हाला चुकून winter बूट आणि summer टोपी मिळू शकत नाही, कारण सगळं त्याच समन्वित संचातून आलं.
🌍
In the wildCross-platform UI toolkits (Swing look-and-feels, Qt styles), cloud-provider abstraction layers, database provider families in ORMs/EF Core (DbProviderFactory), Java's DocumentBuilderFactory/TransformerFactory, and theming systems. Whenever swapping one setting must swap a coordinated group of implementations together, it's an abstract factory.
🌍
प्रत्यक्ष वापरातCross-platform UI toolkits (Swing look-and-feels, Qt styles), cloud-provider abstraction layers, ORMs/EF Core मधली database provider कुटुंबं (DbProviderFactory), Java चं DocumentBuilderFactory/TransformerFactory, आणि theming systems. जेव्हा एक setting बदलणं म्हणजे समन्वित गट बदलणं, तेव्हा तो abstract factory.

✅ Do

  • Use it when several objects must come from the same family
  • Swap the entire family by injecting a different factory
  • Keep the create-methods cohesive (one real family per factory)

✅ हे करा

  • अनेक objects एकाच कुटुंबातून यायला हवेत तेव्हा वापरा
  • वेगळा factory inject करून संपूर्ण कुटुंब बदला
  • create-methods cohesive ठेवा (प्रति factory एक खरं कुटुंब)

❌ Don't

  • Reach for it when you only ever build one kind of object (that's Factory Method)
  • Add a factory family for variation that will never have a second member

❌ हे टाळा

  • तुम्ही फक्त एकच प्रकारचा object बांधता तेव्हा घेऊ नका (तो Factory Method)
  • कधीच दुसरा सदस्य न येणाऱ्या variation साठी factory कुटुंब जोडू नका
Gotcha: Abstract Factory is one of the heaviest creational patterns — lots of interfaces and classes for the guarantee of family-consistency. If you have one product that varies, you want a plain Factory Method (topic 2), not this. And adding a new product to the family (say, secrets()) forces you to edit every factory implementation — so only pay for it when you genuinely have multiple, related products that must stay in lockstep across swappable families.
अडचण: Abstract Factory हा सर्वात जड creational patterns पैकी एक — कुटुंब-सुसंगततेच्या हमीसाठी भरपूर interfaces आणि classes. तुमच्याकडे एक product बदलत असेल, तर साधा Factory Method (topic 2) हवा, हा नाही. आणि कुटुंबात नवीन product जोडणं तुम्हाला प्रत्येक factory implementation edit करायला भाग पाडतं — म्हणून अनेक, संबंधित products lockstep मध्ये राहावेत तेव्हाच याची किंमत मोजा.
04Builder — assemble complex objects step by stepBuilder — जटिल objects टप्प्याटप्प्याने जुळवा★ very common★ खूप सामान्य

Scenario: constructing an HTTP request needs a URL, method, headers, query params, body, timeout, retries — most optional. A constructor with ten positional args (new Request(url, 'POST', null, null, headers, …)) is unreadable and error-prone. A builder assembles it fluently: request().url(u).header('Auth', t).timeout(5000).build().

परिस्थिती: HTTP request बांधायला URL, method, headers, query params, body, timeout, retries लागतात — बहुतेक optional. दहा positional args असलेला constructor अवाचनीय आणि चूक-प्रवण. एक builder ते fluently जुळवतो: request().url(u).header('Auth', t).timeout(5000).build().

What
काय

Builder: separate the step-by-step construction of a complex object from its representation, usually via a fluent chain that ends in build() (which validates invariants).

Builder: जटिल object च्या टप्प्याटप्प्याच्या construction ला त्याच्या representation पासून वेगळं करा, बहुधा fluent chain द्वारे जी build() ने संपते (जो invariants तपासतो).

Why
का

It kills the "telescoping constructor" (many overloads / long positional arg lists), makes optional fields readable, and validates the whole thing once at build() — so you never get a half-configured object.

ते 'telescoping constructor' (अनेक overloads / लांब positional arg याद्या) नष्ट करतं, optional fields वाचनीय करतं, आणि संपूर्ण गोष्ट build() ला एकदा validate करतं — म्हणून तुम्हाला अर्ध-configured object कधीच मिळत नाही.

How
कसं

A builder object accumulates settings via chainable methods (each returns this), then build() constructs and validates the final immutable object.

एक builder object chainable methods द्वारे settings जमवतो (प्रत्येक this परत करतो), मग build() अंतिम immutable object बांधतो आणि validate करतो.

Fluent steps → one validated object
Fluent steps → एक validated object
   request()                     ❌ vs telescoping constructor:
     .url('/orders')                new Request('/orders','POST',null,
     .method('POST')                            headers,null,5000,3,false…)
     .header('Auth', token)           which null is the body? good luck.
     .timeout(5000)
     .retries(3)
     .build()   ← validates here, returns an immutable Request
🧠
Query builders are builders: Knex/jOOQ/SQLAlchemy — db('users').where('active', true).orderBy('name').limit(10) — accumulate a complex SQL statement step by step, then execute. Each call returns the builder so you can keep chaining.
🧠
Query builders म्हणजे builders: Knex/jOOQ/SQLAlchemy — db('users').where('active', true).orderBy('name') — एक जटिल SQL statement टप्प्याटप्प्याने जमवतात, मग execute करतात. प्रत्येक call builder परत करतो म्हणून तुम्ही chain करत राहता.
🌐
HTTP clients ship builders: OkHttp's Request.Builder, Java's HttpRequest.newBuilder(), and axios/fetch config objects all assemble a request from many optional parts and validate at the end.
🌐
HTTP clients builders देतात: OkHttp चा Request.Builder, Java चा HttpRequest.newBuilder(), आणि axios/fetch config objects सगळे अनेक optional भागांतून request जमवतात आणि शेवटी validate करतात.
A fluent request builder that validates at build()
class RequestBuilder {
  private req: any = { method: 'GET', headers: {}, timeout: 30000 }
  url(u: string){ this.req.url = u; return this }          // each step returns this
  method(m: string){ this.req.method = m; return this }
  header(k: string, v: string){ this.req.headers[k] = v; return this }
  timeout(ms: number){ this.req.timeout = ms; return this }
  build(): Request {
    if (!this.req.url) throw new Error('url is required')   // invariant checked ONCE
    return Object.freeze(this.req)                          // immutable result
  }
}
const r = new RequestBuilder().url('/orders').method('POST').header('Auth', tok).build()
🧒
In plain wordsWhen a thing has lots of optional parts, don't cram them all into one giant "make it" call where you can't tell which blank is which. Instead, add the parts one at a time with clear names — "set the URL," "add this header," "set a 5-second timeout" — and then say "build it." At the end it checks everything's sensible and hands you the finished, locked object.
🧒
सोप्या शब्दांतएखाद्या गोष्टीला भरपूर optional भाग असतील, तेव्हा ते सगळे एका भल्या-मोठ्या 'बनव' call मध्ये कोंबू नका जिथे कोणतं blank कोणतं ते कळत नाही. त्याऐवजी भाग एक-एक जोडा स्पष्ट नावांसह — 'URL set कर,' 'हा header जोड' — मग 'build कर' म्हणा. शेवटी ते सगळं ठीक आहे का तपासतं आणि तयार, बंद object देतं.
🌍
In the wildOkHttp Request.Builder, Java HttpRequest/StringBuilder/Stream.Builder, query builders (Knex, jOOQ, SQLAlchemy, ActiveRecord's chainable scopes), Kubernetes manifest builders, protobuf message builders, Flutter widget trees, and "test data builders" (aUser().withEmail(...).build()) that keep test setup readable. Lombok's @Builder generates them automatically.
🌍
प्रत्यक्ष वापरातOkHttp Request.Builder, Java HttpRequest/StringBuilder/Stream.Builder, query builders (Knex, jOOQ, SQLAlchemy, ActiveRecord चे chainable scopes), Kubernetes manifest builders, protobuf message builders, Flutter widget trees, आणि 'test data builders' (aUser().withEmail(...).build()). Lombok चं @Builder ते आपोआप निर्माण करतं.

✅ Do

  • Use it for objects with many optional/complex parts
  • Validate all invariants once in build() and return something immutable
  • Use builders to make test setup readable

✅ हे करा

  • अनेक optional/जटिल भाग असलेल्या objects साठी वापरा
  • सगळे invariants build() मध्ये एकदा validate करा आणि immutable परत करा
  • test setup वाचनीय करायला builders वापरा

❌ Don't

  • Build a builder for a 2-field object — a constructor or object literal is clearer
  • Let the builder return a usable object before build() validates it

❌ हे टाळा

  • 2-field object साठी builder बांधू नका — constructor किंवा object literal स्पष्ट
  • build() validate करण्याआधी builder ला वापरण्याजोगा object परत करू देऊ नका
Gotcha: languages with named/optional/keyword arguments (Python, Kotlin, Swift, TS object params) already solve the "telescoping constructor" problem — Request(url, method='POST', timeout=5000) is as readable as a builder with less ceremony. Reach for a full Builder when you need staged construction, validation at the end, an immutable result, or a fluent DSL (like a query builder). For "just a lot of optional fields," a config object is often enough.
अडचण: named/optional/keyword arguments असलेल्या languages (Python, Kotlin, Swift, TS object params) आधीच 'telescoping constructor' सोडवतात — Request(url, method='POST', timeout=5000) builder इतकंच वाचनीय, कमी सोपस्कारासह. पूर्ण Builder तेव्हा घ्या जेव्हा तुम्हाला staged construction, शेवटी validation, immutable result, किंवा fluent DSL हवं. 'फक्त भरपूर optional fields' साठी config object अनेकदा पुरेसं.
05Singleton — one instance, and why it's often a trapSingleton — एक instance, आणि तो अनेकदा सापळा का⚠ use sparingly⚠ जपून वापरा

Scenario: you want exactly one database connection pool for the whole app, so you make it a global Singleton. Convenient — until tests can't swap it, two modules fight over its state, and it becomes hidden global coupling. Singleton is the most overused and criticized pattern; know it, and prefer DI (topic 18 in OOP) for most cases.

परिस्थिती: तुम्हाला संपूर्ण app साठी नक्की एक database connection pool हवा, म्हणून तुम्ही तो global Singleton करता. सोयीचं — जोपर्यंत tests तो बदलू शकत नाहीत, दोन modules त्याच्या state वरून भांडतात, आणि तो लपलेलं global coupling होतं. Singleton हा सर्वात गैरवापरलेला आणि टीकाकृत pattern; तो जाणा, आणि बहुतेक वेळा DI ला झुकतं द्या (OOP topic 18).

What
काय

Singleton: ensure a class has exactly one instance and provide a global access point to it.

Singleton: एका class ला नक्की एक instance असल्याची खात्री करा आणि त्याला global access point द्या.

Why
का

Some resources are genuinely single (a process-wide connection pool, a config registry, a metrics client). But the "global access point" part causes hidden coupling and testing pain.

काही resources खरोखर एकच असतात (process-wide connection pool, config registry, metrics client). पण 'global access point' भाग लपलेलं coupling आणि testing त्रास घडवतो.

How
कसं

Prefer injecting a single instance created at your composition root over a global getInstance(). If you must use a true Singleton, keep it stateless-ish and behind an interface.

global getInstance() पेक्षा composition root ला बनवलेला एक instance inject करणं ला झुकतं द्या. खरा Singleton लागलाच, तर तो stateless-ish आणि interface मागे ठेवा.

Singleton vs "one instance, injected"
Singleton vs 'एक instance, injected'
  ❌ classic Singleton (global):        ✅ single instance via DI:
     Db.getInstance().query(...)          const db = new Pool(url)   // once, at root
        │  reachable from ANYWHERE         new OrderService(db)      // injected
        │  hidden dependency, hard to      new UserService(db)       // shared, explicit
        ▼  mock in tests, shared mutable   tests: new OrderService(fakeDb)
     global state = spooky coupling      one instance, but dependencies are visible
🧠
A Singleton is a global variable in a nicer suit. The "one instance" part is often fine; the "reachable from anywhere via getInstance()" part is the global-state problem that makes code hard to test and reason about — which is why DI is usually preferred.
🧠
Singleton म्हणजे छान suit घातलेला global variable. 'एक instance' भाग अनेकदा ठीक; 'सगळीकडून getInstance() ने पोहोचता येणारा' भाग म्हणजे global-state समस्या जी code test आणि समजायला कठीण करते — म्हणूनच DI ला झुकतं दिलं जातं.
🧪
The test tells the truth: if you can't replace it with a fake in a unit test without global hacks, it's too global. Injecting "the one pool" gives you singleness and testability — the best of both.
🧪
Test सत्य सांगतो: global hacks शिवाय तुम्ही ते unit test मध्ये fake ने बदलू शकत नसाल, तर ते खूप global. 'तो एक pool' inject करणं एकपणा आणि testability दोन्ही देतं.
The trap vs the preferred approach
// ❌ classic Singleton — global access point, hard to test
class Db {
  private static instance: Db
  static getInstance(){ return Db.instance ??= new Db() }
}
Db.getInstance().query('...')   // called from anywhere; hidden global dependency

// ✅ create ONE instance at the composition root, inject it (topic: DI)
const pool = new Pool(process.env.DATABASE_URL)   // exactly one, made once
const orders = new OrderService(pool)             // dependency is explicit + swappable
// tests: new OrderService(fakePool)              // trivial — no global to stub
🧒
In plain wordsSometimes you truly want only one of something (one shared pool of database connections). The tricky part isn't making one — it's letting the whole program secretly grab it from anywhere, which makes things tangled and hard to test. Better: make the one thing at startup and hand it to whoever needs it. You still have exactly one; you just didn't make it a global that everyone reaches into.
🧒
सोप्या शब्दांतकधीकधी तुम्हाला खरोखर फक्त एकच गोष्ट हवी असते (database connections चा एक सामायिक pool). अवघड भाग तो बनवणं नाही — तर संपूर्ण program ला तो कुठूनही गुपचूप घेऊ देणं, ज्याने गोष्टी गुंतागुंतीच्या आणि test-कठीण होतात. चांगलं: ती एक गोष्ट startup ला बनवा आणि ज्याला लागेल त्याला द्या. तुमच्याकडे अजूनही नक्की एक आहे; तुम्ही फक्त ती सगळे पोहोचू शकतील असं global केलं नाही.
🌍
In the wildLegitimate-ish singles: a process-wide connection pool, logger, config, or metrics client. But Java's Runtime.getRuntime() and many framework globals are cited as cautionary tales; modern DI containers (Spring, .NET, NestJS) provide "singleton scope" that gives you one instance with injection and testability. "Singleton = global state" is a well-known criticism — most style guides say prefer DI.
🌍
प्रत्यक्ष वापरातकायदेशीर-ish एकल: process-wide connection pool, logger, config, किंवा metrics client. पण Java चं Runtime.getRuntime() आणि अनेक framework globals सावधगिरीच्या कथा; modern DI containers (Spring, .NET, NestJS) 'singleton scope' देतात जो तुम्हाला एक instance आणि injection+testability देतो. 'Singleton = global state' ही सुप्रसिद्ध टीका.

✅ Do

  • Prefer a single instance created at the root and injected
  • Use a DI container's singleton scope if you need one instance
  • Keep any true Singleton behind an interface so tests can substitute it

✅ हे करा

  • root ला बनवलेला आणि injected एक instance ला झुकतं द्या
  • एक instance हवा असेल तर DI container चा singleton scope वापरा
  • कुठलाही खरा Singleton interface मागे ठेवा म्हणजे tests बदलू शकतात

❌ Don't

  • Sprinkle getInstance() calls through business logic (hidden global coupling)
  • Store mutable request/user state on a Singleton (thread-safety + test hell)

❌ हे टाळा

  • business logic मध्ये getInstance() calls विखरू नका (लपलेलं global coupling)
  • Singleton वर mutable request/user state ठेवू नका (thread-safety + test नरक)
Gotcha: Singletons hide dependencies and hold global mutable state, which is the root of subtle test flakiness (tests leak state into each other) and concurrency bugs. There's also a lazy-init thread-safety trap: naïve getInstance() can build two instances under a race. If you take one lesson from this card: the value you usually want is "exactly one instance," and you get that far more safely by constructing it once and injecting it than by making it globally reachable. Reserve true Singletons for genuinely process-global, stateless services.
अडचण: Singletons dependencies लपवतात आणि global mutable state धरतात, जे सूक्ष्म test-flakiness आणि concurrency bugs चं मूळ. एक lazy-init thread-safety सापळा ही: भोळं getInstance() race मध्ये दोन instances बांधू शकतं. एक धडा घ्याल तर: तुम्हाला हवं ते value अनेकदा 'नक्की एक instance' असतं, आणि ते global-पोहोचण्याजोगं करण्यापेक्षा एकदा बांधून inject करून खूप सुरक्षितपणे मिळतं.
06Prototype — create by cloning an existing objectPrototype — अस्तित्वात असलेल्या object ला clone करून बनवा

Scenario: you have a fully-configured "default" object — a game enemy with 40 tuned fields, or a base API-request template with auth + retries set — and you need many near-identical copies with small tweaks. Rebuilding each from scratch is wasteful and error-prone. The Prototype pattern clones a preconfigured instance and adjusts the differences.

परिस्थिती: तुमच्याकडे पूर्ण-configured 'default' object आहे — 40 tuned fields असलेला game enemy, किंवा auth + retries set असलेला base API-request template — आणि तुम्हाला लहान बदलांसह अनेक जवळपास-एकसारख्या प्रती हव्यात. प्रत्येक शून्यातून पुन्हा बांधणं वाया आणि चूक-प्रवण. Prototype pattern preconfigured instance clone करतो आणि फरक जुळवतो.

What
काय

Prototype: create new objects by copying an existing "prototype" instance rather than constructing from scratch, then tweak what differs.

Prototype: शून्यातून बांधण्याऐवजी अस्तित्वात असलेल्या 'prototype' instance ला copy करून नवीन objects बनवा, मग जे वेगळं ते जुळवा.

Why
का

When construction is expensive or configuration is elaborate, cloning a known-good instance is faster and avoids re-specifying dozens of fields correctly each time.

construction महाग असेल किंवा configuration विस्तृत असेल, तेव्हा known-good instance clone करणं जलद आणि प्रत्येक वेळी डझनभर fields बरोबर पुन्हा-सांगणं टाळतं.

How
कसं

Give objects a clone() (or use structuredClone/Object.create), keep a registry of prototypes, and clone-then-modify to produce variants.

objects ला clone() द्या (किंवा structuredClone/Object.create वापरा), prototypes चं registry ठेवा, आणि variants बनवायला clone-मग-बदला करा.

Clone a known-good template, tweak the diff
Known-good template clone करा, फरक जुळवा
   prototype:  baseRequest { auth, retries:3, timeout:5s, headers{…} }
                     │ clone()
                     ▼
   variant A = clone(baseRequest); A.url = '/orders'
   variant B = clone(baseRequest); B.url = '/users'; B.timeout = 1s
   40 tuned fields copied for free; you only set what's DIFFERENT
🧠
JavaScript IS prototype-based: objects inherit from other objects via the prototype chain, and Object.create(proto) makes a new object backed by an existing one. Prototype is baked into the language, not bolted on.
🧠
JavaScript prototype-आधारितच आहे: objects prototype chain द्वारे इतर objects पासून inherit करतात, आणि Object.create(proto) अस्तित्वात असलेल्यावर आधारित नवीन object बनवतो. Prototype language मध्ये बेक केलेलं, बोल्ट-ऑन नाही.
🧬
"Duplicate this and change a couple of things": copying a Figma component, "Save As" on a document, or git branch off a known-good commit — start from a configured baseline instead of a blank page.
🧬
'याची नक्कल करून दोन गोष्टी बदल': Figma component copy करणं, document वर 'Save As', किंवा known-good commit वरून git branch — कोऱ्या पानाऐवजी configured baseline पासून सुरू करा.
Clone a configured prototype, then tweak
const baseRequest = {                       // a fully-configured prototype
  headers: { Authorization: token }, retries: 3, timeout: 5000, method: 'GET',
}
// structuredClone gives a deep copy — the base is untouched
const getOrders = { ...structuredClone(baseRequest), url: '/orders' }
const getUser   = { ...structuredClone(baseRequest), url: '/users', timeout: 1000 }
// only the DIFFERENCES are specified; auth/retries/etc. come from the prototype

// classic OO form: objects expose clone()
class Enemy { clone(): Enemy { return structuredClone(this) } }
🧒
In plain wordsIf you already have one thing set up just right and you need lots of almost-identical ones, don't rebuild each from nothing — photocopy the good one and change the few bits that differ. Like keeping a filled-in form as a template and copying it each time instead of writing every field again. Faster, and you won't forget a setting.
🧒
सोप्या शब्दांततुमच्याकडे आधीच एक गोष्ट अगदी बरोबर set केलेली आहे आणि तुम्हाला अनेक जवळपास-एकसारख्या हव्यात, तर प्रत्येक शून्यातून बांधू नका — चांगल्याची photocopy करा आणि जे वेगळे ते थोडे बदला. भरलेला form template म्हणून ठेवून प्रत्येक वेळी copy करण्यासारखं, प्रत्येक field पुन्हा न लिहिता. जलद, आणि एखादं setting विसरणार नाही.
🌍
In the wildJavaScript's prototypal inheritance and Object.create/structuredClone, Java's Cloneable/clone(), C#'s MemberwiseClone/ICloneable, prototype registries in game engines (spawn enemies from a template), deep-copying config templates, and copy-on-write data structures. Kubernetes/IaC "copy this manifest and change the name" is the same instinct.
🌍
प्रत्यक्ष वापरातJavaScript चं prototypal inheritance आणि Object.create/structuredClone, Java चं Cloneable/clone(), C# चं MemberwiseClone/ICloneable, game engines मधली prototype registries, config templates deep-copy करणं, आणि copy-on-write data structures. Kubernetes/IaC 'हा manifest copy करून नाव बदल' तीच वृत्ती.

✅ Do

  • Clone when construction is expensive or configuration is elaborate
  • Keep a registry of prototype instances to copy from
  • Be explicit about deep vs shallow copy

✅ हे करा

  • construction महाग किंवा configuration विस्तृत असेल तेव्हा clone करा
  • copy करायला prototype instances चं registry ठेवा
  • deep vs shallow copy बद्दल स्पष्ट राहा

❌ Don't

  • Clone when a simple constructor call is cheaper and clearer
  • Shallow-copy an object with nested mutable state (aliasing bugs)

❌ हे टाळा

  • साधा constructor call स्वस्त आणि स्पष्ट असेल तेव्हा clone करू नका
  • nested mutable state असलेला object shallow-copy करू नका (aliasing bugs)
Gotcha: the shallow vs deep copy trap is the whole ballgame. A shallow clone copies top-level fields but shares nested objects/arrays by reference — so mutating copy.headers also mutates the prototype's headers, corrupting every future clone. Use structuredClone/deep copy for anything with nested mutable state, and remember that clones skip the constructor, so any invariant checks or side effects in your constructor won't run — validate cloned objects if that matters.
अडचण: shallow vs deep copy सापळा हेच सगळं. shallow clone top-level fields copy करतो पण nested objects/arrays reference ने सामायिक करतो — म्हणून copy.headers mutate केल्याने prototype चे headers ही mutate होतात, प्रत्येक भावी clone बिघडवून. nested mutable state साठी structuredClone/deep copy वापरा, आणि लक्षात ठेवा clones constructor वगळतात, म्हणून constructor मधले invariant checks चालणार नाहीत.
07Object Pool — reuse expensive objectsObject Pool — महाग objects पुनर्वापरा

Scenario: opening a database connection takes 50–100ms (TCP + TLS + auth). If every query opened a fresh one, your API would crawl and the DB would drown in connection churn. An object pool keeps a set of ready connections and lends them out — the single most important reason your app can serve thousands of requests per second.

परिस्थिती: database connection उघडायला 50–100ms लागतात (TCP + TLS + auth). प्रत्येक query ने नवीन उघडली, तर तुमचा API रांगेल आणि DB connection churn मध्ये बुडेल. एक object pool तयार connections चा संच ठेवतो आणि उसने देतो — तुमचा app हजारो requests-per-second सेवू शकण्याचं सर्वात महत्त्वाचं कारण.

What
काय

Object Pool: keep a set of reusable, expensive-to-create objects (connections, threads, buffers) and lend them to clients, who return them instead of destroying them.

Object Pool: पुनर्वापर करण्याजोगे, बांधायला महाग objects (connections, threads, buffers) चा संच ठेवा आणि clients ला उसने द्या, जे ते नष्ट करण्याऐवजी परत करतात.

Why
का

It amortizes expensive setup (TCP/TLS handshakes, thread creation) across many uses and caps resource usage (max connections) so you don't exhaust the DB or OS.

ते महाग setup (TCP/TLS handshakes, thread creation) अनेक वापरांत विभागतं आणि resource वापर मर्यादित करतं (max connections) म्हणून तुम्ही DB किंवा OS संपवत नाही.

How
कसं

A pool tracks idle + in-use objects; acquire() hands out (or creates/blocks up to a max), release() returns it for reuse. Health-check on return.

pool idle + in-use objects track करतो; acquire() देतो (किंवा max पर्यंत बनवतो/block करतो), release() पुनर्वापरासाठी परत करतो. परत आल्यावर health-check.

Borrow → use → return, capped at a max
उसना घ्या → वापरा → परत करा, max ला मर्यादित
   ┌──── Connection Pool (max 20) ────┐
   │ idle: [c1 c2 c3]   in-use: [c4…] │
   └──────────────────────────────────┘
   acquire() ─► hands out c1 (or creates if under max, or WAITS if maxed)
   ...query...
   release(c1) ─► c1 goes back to idle (health-checked), ready for the next caller
   50ms handshake paid ONCE per connection, not once per query
🧠
Connection pools are why servers scale: HikariCP, pgbouncer, and every ORM's pool keep warm DB connections. Without pooling, connection setup cost and the DB's connection limit would cap you at a fraction of your throughput.
🧠
Connection pools मुळेच servers scale होतात: HikariCP, pgbouncer, आणि प्रत्येक ORM चा pool उबदार DB connections ठेवतात. Pooling शिवाय connection setup खर्च आणि DB ची connection limit तुम्हाला तुमच्या throughput च्या अंशावर मर्यादित करतील.
🧵
Thread pools are the same idea: creating an OS thread per task is expensive, so executors keep a fixed pool of worker threads and feed them a task queue — reuse over re-create, with a hard cap.
🧵
Thread pools तीच कल्पना: प्रति task OS thread बनवणं महाग, म्हणून executors worker threads चा निश्चित pool ठेवतात आणि त्यांना task queue देतात — पुनर्वापर वि. पुनर्निर्मिती, कठोर मर्यादेसह.
Acquire / release with a max size
class ConnectionPool {
  private idle: Conn[] = []
  private inUse = 0
  constructor(private max: number, private make: () => Conn) {}
  async acquire(): Promise<Conn> {
    if (this.idle.length) return this.idle.pop()!          // reuse a warm one
    if (this.inUse < this.max) { this.inUse++; return this.make() }  // or create
    return this.waitForRelease()                          // or WAIT (backpressure)
  }
  release(c: Conn){ if (c.healthy()) this.idle.push(c); else this.inUse-- } // return, checked
}
// the 50ms TCP+TLS+auth handshake is amortized across thousands of queries
🧒
In plain wordsSome things are slow and pricey to set up (like a phone call that takes a while to connect). Instead of hanging up and redialing for every little thing, you keep a few lines already open and share them: grab a free line, use it, hand it back. That's how a website can handle tons of visitors — it isn't opening a brand-new database connection for every single click.
🧒
सोप्या शब्दांतकाही गोष्टी set करायला मंद आणि महाग असतात (जोडायला वेळ लागणाऱ्या फोन-call सारखं). प्रत्येक लहान गोष्टीसाठी फोन ठेवून पुन्हा-dial करण्याऐवजी, तुम्ही काही lines आधीच उघड्या ठेवून सामायिक करता: मोकळी line घ्या, वापरा, परत द्या. म्हणूनच website टन visitors हाताळू शकते — ती प्रत्येक click साठी नवीन database connection उघडत नाही.
🌍
In the wildDatabase connection pools (HikariCP, pgbouncer, node-postgres Pool, ActiveRecord/SQLAlchemy pools), HTTP keep-alive connection pools (OkHttp, undici), thread pools/executors (Java ExecutorService, .NET ThreadPool), byte-buffer pools (Netty), and game-engine object pools for bullets/particles. It's foundational to the scaling story in the Distributed Systems playbook.
🌍
प्रत्यक्ष वापरातDatabase connection pools (HikariCP, pgbouncer, node-postgres Pool, ActiveRecord/SQLAlchemy pools), HTTP keep-alive connection pools (OkHttp, undici), thread pools/executors (Java ExecutorService, .NET ThreadPool), byte-buffer pools (Netty), आणि game-engine object pools. Distributed Systems playbook मधल्या scaling कथेचा पाया.

✅ Do

  • Pool genuinely expensive resources (connections, threads, big buffers)
  • Cap the pool size to protect the downstream resource; apply backpressure when maxed
  • Health-check objects on return and reset their state before reuse

✅ हे करा

  • खरोखर महाग resources pool करा (connections, threads, मोठे buffers)
  • downstream resource रक्षायला pool आकार मर्यादित करा; maxed झाल्यावर backpressure लावा
  • परत आल्यावर objects health-check करा आणि पुनर्वापराआधी state reset करा

❌ Don't

  • Pool cheap objects — modern allocators/GC make that a pessimization
  • Forget to release() (a leak that exhausts the pool and hangs the app)

❌ हे टाळा

  • स्वस्त objects pool करू नका — modern allocators/GC ते pessimization करतात
  • release() विसरू नका (pool संपवणारा आणि app hang करणारा leak)
Gotcha: the two classic bugs are leaks and dirty state. Forget to release() (e.g. on an error path without try/finally) and the pool drains until every request blocks forever waiting for a connection — a nasty production hang. And a returned object carries its old state: a DB connection left mid-transaction, a buffer with stale bytes. Always reset/health-check on return, always release in a finally, and size the pool deliberately — too small starves throughput, too large overwhelms the DB (topic: Distributed Systems). Don't pool trivially cheap objects; you'll add complexity and often lose to the GC.
अडचण: दोन classic bugs म्हणजे leaks आणि dirty state. release() विसरा (उदा. try/finally शिवाय error path वर) आणि pool आटतो जोपर्यंत प्रत्येक request connection ची वाट पाहत block होत नाही — एक वाईट production hang. आणि परत आलेला object त्याचं जुनं state वाहतो: mid-transaction सोडलेली DB connection. परत आल्यावर नेहमी reset/health-check करा, नेहमी finally मध्ये release करा, आणि pool मुद्दाम आकारा — लहान throughput उपाशी ठेवतं, मोठं DB बुडवतं.
Part II · Structural — how objects compose
भाग II · Structural — objects कसे compose होतात
08Adapter — make an incompatible interface fitAdapter — विसंगत interface जुळवा★ core★ गाभा

Scenario: your app talks to a PaymentGateway interface (topic: abstraction), but Stripe's SDK has its own method names and shapes (stripe.charges.create({amount, source})). An adapter is the thin translation layer that makes Stripe's API look like your PaymentGateway — without changing either side.

परिस्थिती: तुमचा app PaymentGateway interface शी बोलतो, पण Stripe चा SDK त्याची स्वतःची method-नावं आणि आकार वापरतो (stripe.charges.create({amount, source})). एक adapter म्हणजे तो पातळ भाषांतर-स्तर जो Stripe चा API तुमच्या PaymentGateway सारखा दिसवतो — दोन्ही बाजू न बदलता.

What
काय

Adapter: wrap an object with an incompatible interface in a class that translates calls to the interface your code expects. (a.k.a. Wrapper.)

Adapter: विसंगत interface असलेला object एका class मध्ये गुंडाळा जी calls तुमच्या अपेक्षित interface मध्ये भाषांतरित करते. (उर्फ Wrapper.)

Why
का

It lets you integrate third-party or legacy code that you can't change, and keeps vendor-specific shapes out of your business logic (one translation point).

ते तुम्हाला न बदलता येणारा third-party किंवा legacy code एकत्र करू देतं, आणि vendor-विशिष्ट आकार तुमच्या business logic बाहेर ठेवतं (एक भाषांतर-बिंदू).

How
कसं

Implement your target interface; inside, delegate to the adaptee, mapping arguments and return values (and errors) between the two vocabularies.

तुमचं target interface राबवा; आत, adaptee ला delegate करा, दोन शब्दसंग्रहांमध्ये arguments आणि return values (आणि errors) map करून.

Translate their shape into yours
त्यांचा आकार तुमच्यात भाषांतरित करा
   your code ──► «PaymentGateway.charge(Money, token)»   (what you want)
                        │  implemented by
                 StripeAdapter
                        │  translates → stripe.charges.create({amount, source})
                        ▼
                 Stripe SDK (the adaptee — unchanged, incompatible shape)
🧠
A travel plug adapter is the literal metaphor: it doesn't change the wall socket or your device — it sits between them and makes the shapes fit. A code adapter changes neither the SDK nor your interface; it bridges their signatures.
🧠
प्रवासाचा plug adapter हेच रूपक: तो wall socket किंवा तुमचं उपकरण बदलत नाही — तो मध्ये बसून आकार जुळवतो. Code adapter SDK किंवा तुमचं interface बदलत नाही; तो त्यांच्या signatures ला पूल घालतो.
🔌
ORMs adapt databases: a PostgresAdapter, MySQLAdapter, SQLiteAdapter each make a specific driver satisfy the ORM's uniform interface — so the ORM core speaks one language while the drivers differ.
🔌
ORMs databases जुळवतात: एक PostgresAdapter, MySQLAdapter, SQLiteAdapter प्रत्येक विशिष्ट driver ला ORM च्या एकसमान interface शी जुळवतो — म्हणून ORM core एक भाषा बोलतो तर drivers वेगळे.
Adapter: your interface, their SDK underneath
interface PaymentGateway { charge(amount: Money, token: string): Promise<Receipt> }

class StripeAdapter implements PaymentGateway {          // YOUR interface
  constructor(private stripe: Stripe) {}
  async charge(amount: Money, token: string): Promise<Receipt> {
    const res = await this.stripe.charges.create({        // THEIR shape
      amount: amount.cents, currency: amount.currency, source: token,
    })
    return { id: res.id, status: res.status }             // translate back to YOURS
  }
}
// business code depends only on PaymentGateway; Stripe's vocabulary stops here
🧒
In plain wordsTwo things that should work together speak different "languages." Rather than rewrite either one, you put a little translator in the middle that turns your requests into theirs and their answers back into yours. Like a travel adapter for a foreign plug — nothing about the wall or your charger changes, the adapter just makes them fit.
🧒
सोप्या शब्दांतदोन गोष्टी ज्या एकत्र काम करायला हव्यात त्या वेगळ्या 'भाषा' बोलतात. एक पुन्हा लिहिण्याऐवजी, तुम्ही मध्ये एक छोटा भाषांतरकार ठेवता जो तुमच्या विनंत्या त्यांच्यात आणि त्यांची उत्तरं तुमच्यात बदलतो. परदेशी plug साठी प्रवास-adapter सारखं — wall किंवा तुमचा charger बदलत नाही, adapter फक्त त्यांना जुळवतो.
🌍
In the wildORM database adapters (ActiveRecord, SQLAlchemy dialects), logging facades (SLF4J adapting Logback/Log4j), java.io.InputStreamReader (bytes→chars), Rack/WSGI adapting web servers to apps, wrapping any third-party/legacy SDK behind your own interface, and React wrappers around non-React widgets. Any "*Adapter"/"*Wrapper" class translating one API to another is this.
🌍
प्रत्यक्ष वापरातORM database adapters (ActiveRecord, SQLAlchemy dialects), logging facades (SLF4J जे Logback/Log4j जुळवतं), java.io.InputStreamReader (bytes→chars), Rack/WSGI, कुठलाही third-party/legacy SDK तुमच्या interface मागे गुंडाळणं, आणि non-React widgets भोवतीचे React wrappers. एक API दुसऱ्यात भाषांतरित करणारी कुठलीही "*Adapter"/"*Wrapper" class हीच.

✅ Do

  • Use adapters to keep third-party/vendor shapes at the edges
  • Translate arguments, return values, and errors into your vocabulary
  • Keep one adapter per external system you integrate

✅ हे करा

  • third-party/vendor आकार edges ला ठेवायला adapters वापरा
  • arguments, return values, आणि errors तुमच्या शब्दसंग्रहात भाषांतरित करा
  • integrate करता त्या प्रति external system एक adapter ठेवा

❌ Don't

  • Let the adapter leak the adaptee's types (that defeats it — a leaky abstraction)
  • Add an adapter when you actually control both sides and could just align them

❌ हे टाळा

  • adapter ला adaptee चे types गळू देऊ नका (ते व्यर्थ — leaky abstraction)
  • तुम्ही दोन्ही बाजू नियंत्रित करता आणि जुळवू शकता तेव्हा adapter जोडू नका
Gotcha: Adapter vs its neighbors trips people up. Adapter changes an interface to a different one you already need (integration); Decorator (topic 9) keeps the same interface but adds behavior; Facade (topic 10) invents a simpler interface over a complex subsystem. If you're translating shapes, it's an Adapter. And beware the leaky adapter: if it returns the adaptee's raw error/response types, callers still couple to the vendor and the whole point is lost — map everything at the boundary.
अडचण: Adapter vs त्याचे शेजारी गोंधळवतात. Adapter interface तुम्हाला आधीच लागणाऱ्या वेगळ्यात बदलतो (integration); Decorator (topic 9) तोच interface ठेवतो पण behavior जोडतो; Facade (topic 10) जटिल subsystem वर एक साधं interface शोधतो. आकार भाषांतरित करत असाल, तर तो Adapter. आणि leaky adapter पासून सावध: adaptee चे raw error/response types परत केले, तर callers अजूनही vendor शी जोडलेले — सीमेवर सगळं map करा.
09Decorator — add behavior by wrappingDecorator — गुंडाळून behavior जोडा★ very common★ खूप सामान्य

Scenario: you have an HttpClient, and you want to add retries, then caching, then auth headers, then request logging — composably, so you can mix and match per environment. A decorator wraps the client in another object with the same interface that adds one behavior and delegates the rest. Stack them like layers.

परिस्थिती: तुमच्याकडे HttpClient आहे, आणि तुम्हाला retries, मग caching, मग auth headers, मग request logging जोडायचं आहे — composably, म्हणजे प्रति environment mix-and-match. एक decorator client ला तोच interface असलेल्या दुसऱ्या object मध्ये गुंडाळतो जो एक behavior जोडतो आणि बाकी delegate करतो. त्यांना थरांसारखं रचा.

What
काय

Decorator: wrap an object in another object implementing the same interface, adding behavior before/after delegating. Decorators nest, so behaviors stack.

Decorator: object ला तोच interface राबवणाऱ्या दुसऱ्या object मध्ये गुंडाळा, delegate करण्याआधी/नंतर behavior जोडून. Decorators nest होतात, म्हणून behaviors रचली जातात.

Why
का

It adds responsibilities at runtime without subclass explosion (the composition-over-inheritance win): retry + cache + log = three small wrappers, any combination.

ते subclass-स्फोट न करता runtime ला जबाबदाऱ्या जोडतं (composition-over-inheritance फायदा): retry + cache + log = तीन लहान wrappers, कुठलाही संयोग.

How
कसं

Each decorator takes the wrapped object, implements the interface, does its extra bit, and calls through. Compose by nesting: log(cache(retry(client))).

प्रत्येक decorator गुंडाळलेला object घेतो, interface राबवतो, त्याचं अतिरिक्त करतो, आणि पुढे call करतो. Nest करून compose करा: log(cache(retry(client))).

Same interface, stacked layers
तोच interface, रचलेले थर
   request ─► LoggingClient ─► CachingClient ─► RetryClient ─► HttpClient
                 │ log            │ hit? return   │ try 3×        │ real fetch
                 ▼ delegate       ▼ else delegate  ▼ delegate      ▼
   every layer is an HttpClient; caller can't tell how many wrappers are stacked
   mix per env: prod = log(cache(retry(c)))   test = c   (no wrappers)
🧠
Express/Rack middleware IS the decorator pattern: each middleware wraps the next, adding auth, gzip, logging around the request — a stack you compose. Same for HTTP client interceptors (axios, OkHttp) that layer retry/auth/tracing.
🧠
Express/Rack middleware म्हणजेच decorator pattern: प्रत्येक middleware पुढच्याला गुंडाळतं, request भोवती auth, gzip, logging जोडून — एक stack जो तुम्ही compose करता. HTTP client interceptors (axios, OkHttp) साठी तेच.
Java I/O streams are the canonical example: new BufferedReader(new InputStreamReader(new FileInputStream(f))) — each wrapper adds buffering / decoding while keeping the Reader/InputStream interface. Python's @decorator syntax wraps functions the same way.
Java I/O streams हे canonical उदाहरण: new BufferedReader(new InputStreamReader(new FileInputStream(f))) — प्रत्येक wrapper buffering/decoding जोडतो, Reader/InputStream interface ठेवून. Python चं @decorator syntax functions ला तसंच गुंडाळतं.
Wrap to add retry, then caching — same interface
interface HttpClient { get(url: string): Promise<Response> }

class RetryClient implements HttpClient {                 // adds retries
  constructor(private inner: HttpClient, private times = 3) {}
  async get(url: string) {
    for (let i = 0; i < this.times; i++) {
      try { return await this.inner.get(url) } catch (e) { if (i === this.times-1) throw e }
    }
    throw new Error('unreachable')
  }
}
class CachingClient implements HttpClient {               // adds caching
  private cache = new Map<string, Response>()
  constructor(private inner: HttpClient) {}
  async get(url: string){ return this.cache.get(url) ?? this.set(url) }
  private async set(u: string){ const r = await this.inner.get(u); this.cache.set(u,r); return r }
}
// compose: const client = new CachingClient(new RetryClient(new RealHttpClient()))
🧒
In plain wordsTake something that works, and wrap it in a layer that adds one extra ability — like putting a phone in a waterproof case, then a grippy case, then a wallet case. Each layer still acts like "the phone," but adds a feature. In code you stack wrappers (retry, cache, logging) around a client and mix them however you like, without inventing a new class for every combination.
🧒
सोप्या शब्दांतएखादी गोष्ट जी काम करते ती घ्या, आणि तिला एका थरात गुंडाळा जो एक अतिरिक्त क्षमता जोडतो — फोनला waterproof case, मग grippy case, मग wallet case लावण्यासारखं. प्रत्येक थर अजूनही 'फोन' सारखा वागतो, पण एक feature जोडतो. Code मध्ये तुम्ही client भोवती wrappers (retry, cache, logging) रचता आणि हवं तसं mix करता, प्रत्येक संयोगासाठी नवीन class न शोधता.
🌍
In the wildExpress/Koa/Rack middleware, HTTP interceptors (axios, OkHttp, gRPC), Java I/O streams (BufferedReader, GZIPInputStream), Python function @decorators, React Higher-Order Components and hooks that wrap behavior, Redux middleware, and resilience wrappers (retry/cache/circuit-breaker layered around a client — see topic 27). It's composition-over-inheritance made concrete.
🌍
प्रत्यक्ष वापरातExpress/Koa/Rack middleware, HTTP interceptors (axios, OkHttp, gRPC), Java I/O streams (BufferedReader, GZIPInputStream), Python function @decorators, React Higher-Order Components आणि behavior गुंडाळणारे hooks, Redux middleware, आणि resilience wrappers (client भोवती retry/cache/circuit-breaker — topic 27). Composition-over-inheritance प्रत्यक्षात.

✅ Do

  • Use decorators to add cross-cutting behavior (retry, cache, log, auth) composably
  • Keep each decorator to one responsibility and delegate the rest
  • Compose per environment (prod stacks wrappers; tests use the bare object)

✅ हे करा

  • cross-cutting behavior (retry, cache, log, auth) composably जोडायला decorators वापरा
  • प्रत्येक decorator एका जबाबदारीपुरता ठेवा आणि बाकी delegate करा
  • प्रति environment compose करा (prod wrappers रचतं; tests bare object वापरतात)

❌ Don't

  • Change the interface in a decorator (that would make it an Adapter, not a Decorator)
  • Stack so many layers that debugging a call means peeling ten wrappers

❌ हे टाळा

  • decorator मध्ये interface बदलू नका (ते Adapter करेल, Decorator नाही)
  • इतके थर रचू नका की एका call चं debugging म्हणजे दहा wrappers सोलणं
Gotcha: deeply nested decorators make stack traces and debugging painful — a failing request may pass through eight wrappers, and it's hard to see which layer misbehaved or what order they run in (order matters: caching-outside-retry vs retry-outside-caching behave differently). Keep the stack shallow and its composition explicit in one place. Also note the naming clash: Python/TS @decorator syntax and Java annotations are related but not identical to the GoF object-wrapping Decorator — same spirit, different mechanism.
अडचण: खोलवर nested decorators stack traces आणि debugging कष्टकर करतात — अपयशी request आठ wrappers मधून जाऊ शकते, आणि कोणता थर बिघडला किंवा ते कोणत्या क्रमाने चालतात हे पाहणं कठीण (क्रम महत्त्वाचा: caching-बाहेर-retry vs retry-बाहेर-caching वेगळे वागतात). stack उथळ आणि त्याची composition एका ठिकाणी स्पष्ट ठेवा. आणि नाव-गोंधळ लक्षात घ्या: Python/TS @decorator syntax आणि GoF object-wrapping Decorator संबंधित पण एकसारखे नाहीत.
10Facade — one simple door over a complex subsystemFacade — जटिल subsystem वर एक साधं दार

Scenario: placing an order really means: validate the cart, reserve inventory, charge payment, create a shipment, send a confirmation email, and emit analytics — six subsystems. Callers shouldn't orchestrate all that. A facadecheckout.placeOrder(cart, payment) — presents one simple method that coordinates the mess behind it.

परिस्थिती: order place करणं म्हणजे खरंतर: cart validate करणं, inventory राखणं, payment charge करणं, shipment बनवणं, confirmation email पाठवणं, आणि analytics emit करणं — सहा subsystems. Callers ने ते सगळं orchestrate करू नये. एक facadecheckout.placeOrder(cart, payment) — एक साधी method देतो जी मागचा गोंधळ समन्वयित करते.

What
काय

Facade: a single, simplified interface in front of a complex subsystem, hiding its many moving parts behind one coherent entry point.

Facade: जटिल subsystem समोर एक साधं, सरलीकृत interface, त्याचे अनेक हलणारे भाग एका सुसंगत entry-point मागे लपवून.

Why
का

It reduces the surface callers must understand, decouples them from subsystem details, and gives you one place to change the orchestration.

ते callers ना समजावं लागणारं surface कमी करतं, त्यांना subsystem तपशीलांपासून decouple करतं, आणि orchestration बदलायला एक जागा देतं.

How
कसं

Create a class whose methods coordinate the subsystems for common use cases. Callers use the facade; the subsystems stay usable directly for advanced needs.

अशी class बनवा जिच्या methods सामान्य use-cases साठी subsystems समन्वयित करतात. Callers facade वापरतात; subsystems advanced गरजांसाठी थेट वापरण्याजोगे राहतात.

One method, many subsystems behind it
एक method, मागे अनेक subsystems
   caller ──► checkout.placeOrder(cart, payment)     ← the ONE simple door
                        │  orchestrates:
        ┌───────┬───────┼────────┬─────────┬──────────┐
     Inventory  Pricing Payments  Shipping  Email    Analytics
   caller doesn't know or care about the six subsystems — just the facade method
🧠
An SDK client is a facade: the Stripe/AWS SDK gives you stripe.charges.create(...) over a pile of HTTP, auth, retries, and serialization. jQuery was a famous facade over inconsistent DOM/AJAX APIs. You call one friendly method; it hides the machinery.
🧠
SDK client म्हणजे facade: Stripe/AWS SDK तुम्हाला HTTP, auth, retries, serialization च्या ढिगावर stripe.charges.create(...) देतो. jQuery हा विसंगत DOM/AJAX APIs वरचा प्रसिद्ध facade होता. तुम्ही एक मैत्रीपूर्ण method call करता; ती यंत्रणा लपवते.
🚪
A hotel concierge: you say "arrange dinner and a taxi at 7," and one person coordinates the restaurant, the car, and the timing. You didn't call three vendors — you used one simplified interface.
🚪
हॉटेल concierge: तुम्ही म्हणता '7 वाजता dinner आणि taxi ठरव,' आणि एक व्यक्ती restaurant, गाडी, आणि वेळ समन्वयित करते. तुम्ही तीन vendors ना call केलं नाही — एक सरलीकृत interface वापरलं.
A checkout facade coordinating six subsystems
class CheckoutFacade {
  constructor(
    private inventory: Inventory, private pricing: Pricing, private payments: Payments,
    private shipping: Shipping, private email: Mailer, private analytics: Analytics,
  ) {}
  async placeOrder(cart: Cart, payment: PaymentInfo): Promise<Order> {   // the simple door
    await this.inventory.reserve(cart)
    const total = this.pricing.totalFor(cart)
    const receipt = await this.payments.charge(total, payment)
    const order = Order.create(cart, receipt)
    await this.shipping.schedule(order)
    await this.email.sendConfirmation(order)
    this.analytics.track('order.placed', order)
    return order
  }
}
// callers: checkout.placeOrder(cart, payment) — that's the whole surface
🧒
In plain wordsBehind a big task there are often lots of little steps and systems. A facade is a single friendly button that does the whole thing for you — "place the order" — while quietly handling all the steps (stock, payment, shipping, email) in the background. You press one button instead of learning to operate six machines.
🧒
सोप्या शब्दांतमोठ्या कामामागे अनेकदा भरपूर लहान steps आणि systems असतात. Facade म्हणजे एक मैत्रीपूर्ण button जो संपूर्ण गोष्ट तुमच्यासाठी करतो — 'order place कर' — तर मागे सगळे steps (stock, payment, shipping, email) शांतपणे हाताळतो. तुम्ही सहा यंत्रं चालवायला शिकण्याऐवजी एक button दाबता.
🌍
In the wildSDK clients (Stripe, AWS, Twilio) over raw HTTP, jQuery over the DOM, ORMs over SQL, a service-layer/use-case class over multiple repositories, high-level libraries like requests over urllib, and API gateways / backend-for-frontend endpoints that present one call over many microservices. Any "just call this one thing" wrapper over a tangle is a facade.
🌍
प्रत्यक्ष वापरातSDK clients (Stripe, AWS, Twilio) raw HTTP वर, jQuery DOM वर, ORMs SQL वर, अनेक repositories वर एक service-layer/use-case class, requests सारख्या high-level libraries urllib वर, आणि अनेक microservices वर एक call देणारे API gateways / backend-for-frontend endpoints. गुंत्यावर कुठलाही 'फक्त हे एक call कर' wrapper हा facade.

✅ Do

  • Provide a facade for the common workflows over a complex subsystem
  • Keep the subsystems usable directly for advanced/edge cases
  • Centralize orchestration in the facade so it's changed in one place

✅ हे करा

  • जटिल subsystem वरच्या सामान्य workflows साठी facade द्या
  • subsystems advanced/edge cases साठी थेट वापरण्याजोगे ठेवा
  • orchestration facade मध्ये केंद्रित करा म्हणजे ते एका ठिकाणी बदलतं

❌ Don't

  • Let the facade swell into a God object owning every rule (topic 28)
  • Force everything through the facade if power users need direct access

❌ हे टाळा

  • facade ला प्रत्येक नियमाचा मालक God object होऊ देऊ नका (topic 28)
  • power users ना थेट access हवा असेल तर सगळं facade मधून जबरदस्ती करू नका
Gotcha: a facade that keeps absorbing responsibilities becomes a God object (topic 28) — the "simple door" turns into a 2,000-line class every feature must edit, re-coupling everything you meant to decouple. Keep the facade thin: it orchestrates subsystems, it doesn't implement their logic. Also distinguish it from Adapter (topic 8): a facade simplifies an interface for convenience; an adapter converts one interface to a specific other one you're required to match.
अडचण: जबाबदाऱ्या शोषत राहणारा facade God object होतो (topic 28) — 'साधं दार' 2,000-ओळींची class होते जिला प्रत्येक feature edit करावं लागतं, तुम्ही decouple करू पाहत असलेलं सगळं पुन्हा जोडून. facade पातळ ठेवा: तो subsystems समन्वयित करतो, त्यांचं logic राबवत नाही. Adapter (topic 8) पासूनही वेगळा करा: facade सोयीसाठी interface सरलीकृत करतो; adapter एक interface विशिष्ट दुसऱ्यात बदलतो.
11Proxy — a stand-in that controls accessProxy — access नियंत्रित करणारा stand-in

Scenario: loading a user's orders from the DB is expensive, and you often don't touch them. An ORM gives you a proxy: a stand-in that looks like the orders collection but only hits the database the moment you actually access it (lazy loading). A proxy has the same interface as the real object but controls access to it.

परिस्थिती: user चे orders DB मधून load करणं महाग आहे, आणि तुम्ही अनेकदा त्यांना स्पर्श करत नाही. ORM तुम्हाला proxy देतो: orders collection सारखा दिसणारा stand-in जो तुम्ही खरंच access करताच database ला hit करतो (lazy loading). Proxy ला real object सारखा तोच interface असतो पण तो त्याचा access नियंत्रित करतो.

What
काय

Proxy: a placeholder with the same interface as a real object that controls access to it — adding lazy loading, caching, access control, or remote calls, transparently.

Proxy: real object सारखा तोच interface असलेला placeholder जो त्याचा access नियंत्रित करतो — lazy loading, caching, access control, किंवा remote calls पारदर्शकपणे जोडून.

Why
का

Callers use the object normally while the proxy handles cross-cutting concerns — defer expensive work, cache results, enforce permissions, or make a remote object look local.

Callers object normally वापरतात तर proxy cross-cutting concerns हाताळतो — महाग काम पुढे ढकला, results cache करा, permissions लागू करा, किंवा remote object local सारखा दिसवा.

How
कसं

Implement the target interface; hold (or lazily create) the real subject; intercept calls to add the concern, then delegate.

target interface राबवा; real subject धरा (किंवा lazily बनवा); calls अडवून concern जोडा, मग delegate करा.

Same face, controlled access
तोच चेहरा, नियंत्रित access
   caller ──► «Orders» (interface)
                  │
             OrdersProxy   ── first access? ──► load from DB, cache, delegate
                  │         ── cached?      ──► return without touching DB
                  ▼         ── unauthorized?──► throw
             RealOrders (the expensive/remote/protected subject)
🧠
ORM lazy loading is a proxy: Hibernate/ActiveRecord hand you a proxy for a related collection that runs the query only on first access. JavaScript's built-in Proxy intercepts property reads/writes to add validation, logging, or reactivity (Vue's reactivity is built on it).
🧠
ORM lazy loading म्हणजे proxy: Hibernate/ActiveRecord तुम्हाला संबंधित collection साठी proxy देतात जो पहिल्या access ला query चालवतो. JavaScript चा built-in Proxy property reads/writes अडवून validation, logging, किंवा reactivity जोडतो (Vue ची reactivity यावर बांधलेली).
🛡️
An API gateway / reverse proxy (nginx, Envoy) stands in front of your services, adding auth, rate limiting, caching, and routing — the client thinks it's talking to the service, but the proxy controls access. Same pattern, network scale.
🛡️
API gateway / reverse proxy (nginx, Envoy) तुमच्या services समोर उभा राहतो, auth, rate limiting, caching, आणि routing जोडून — client ला वाटतं तो service शी बोलतोय, पण proxy access नियंत्रित करतो. तोच pattern, network-scale.
A lazy-loading (virtual) proxy
interface Orders { all(): Promise<Order[]> }

class OrdersProxy implements Orders {                 // same interface as the real thing
  private real?: RealOrders
  private cached?: Order[]
  constructor(private userId: string, private db: Db) {}
  async all(): Promise<Order[]> {
    if (this.cached) return this.cached                // caching concern
    this.real ??= new RealOrders(this.userId, this.db) // lazy-create the subject
    this.cached = await this.real.all()                // defer the expensive query
    return this.cached
  }
}
// user.orders is an OrdersProxy — the DB is hit only when you call .all()
🧒
In plain wordsA proxy is a stand-in that looks and acts exactly like the real thing, but sits in front to control what happens. Maybe it waits to fetch the real thing until you actually need it, or remembers the answer so it doesn't re-fetch, or checks whether you're allowed. You talk to the stand-in as if it were the real object — it quietly manages the access behind the scenes.
🧒
सोप्या शब्दांतProxy म्हणजे एक stand-in जो खऱ्या गोष्टीसारखाच दिसतो आणि वागतो, पण समोर बसून काय होतं ते नियंत्रित करतो. कदाचित तो खरी गोष्ट तुम्हाला खरंच लागेपर्यंत आणायची थांबतो, किंवा उत्तर लक्षात ठेवतो म्हणजे पुन्हा-fetch करत नाही, किंवा तुम्हाला परवानगी आहे का तपासतो. तुम्ही stand-in शी बोलता जणू तो खरा object; तो शांतपणे access सांभाळतो.
🌍
In the wildORM lazy loading (Hibernate/ActiveRecord proxies), JavaScript's Proxy (Vue/MobX reactivity, validation), Spring AOP dynamic proxies (transactions, @Cacheable, security), gRPC/RMI client stubs (remote proxy makes a network object look local), reverse proxies / API gateways (nginx, Envoy) for auth & caching, and CDN edge caches. Protection, virtual (lazy), caching, and remote proxies are the common flavors.
🌍
प्रत्यक्ष वापरातORM lazy loading (Hibernate/ActiveRecord proxies), JavaScript चा Proxy (Vue/MobX reactivity, validation), Spring AOP dynamic proxies (transactions, @Cacheable, security), gRPC/RMI client stubs, reverse proxies / API gateways (nginx, Envoy), आणि CDN edge caches. Protection, virtual (lazy), caching, आणि remote proxies हे सामान्य प्रकार.

✅ Do

  • Use a proxy to add lazy loading, caching, access control, or remote access transparently
  • Keep the proxy's interface identical to the real subject's
  • Isolate the cross-cutting concern in the proxy, not the business object

✅ हे करा

  • lazy loading, caching, access control, किंवा remote access पारदर्शकपणे जोडायला proxy वापरा
  • proxy चं interface real subject सारखं एकसारखं ठेवा
  • cross-cutting concern proxy मध्ये वेगळं करा, business object मध्ये नाही

❌ Don't

  • Hide expensive/remote work so well that callers get surprise latency (the "N+1 via lazy loading" trap)
  • Put business logic in a proxy — it should only control access

❌ हे टाळा

  • महाग/remote काम इतकं लपवू नका की callers ना surprise latency मिळेल (lazy loading द्वारे N+1)
  • proxy मध्ये business logic ठेवू नका — तो फक्त access नियंत्रित करावा
Gotcha: the same transparency that makes proxies elegant hides cost. Lazy-loading proxies are the classic cause of the N+1 query problem (see the N+1 case study): a loop that touches user.orders for 100 users silently fires 100 queries, because each access transparently hits the DB. Proxies also complicate ==/identity (a proxy isn't the real object) and debugging (the object isn't what it appears). Use them deliberately, and know when a proxy is doing expensive work behind an innocent-looking property access.
अडचण: जी पारदर्शकता proxies ला सुंदर करते तीच खर्च लपवते. Lazy-loading proxies हे N+1 query problem चं classic कारण (N+1 case study पाहा): 100 users साठी user.orders touch करणारा loop शांतपणे 100 queries चालवतो, कारण प्रत्येक access पारदर्शकपणे DB hit करतो. Proxies ==/identity ही गुंतागुंतीचं करतात आणि debugging (object जो दिसतो तो नसतो). मुद्दाम वापरा.
12Composite — treat trees and leaves uniformlyComposite — trees आणि leaves एकसारखे वागवा

Scenario: a UI is a tree — a Panel contains Buttons and other Panels, nested arbitrarily. You want to call render() (or size()) on the whole tree without special-casing "is this a container or a leaf?" The Composite pattern lets a group of objects be treated the same as a single object.

परिस्थिती: एक UI म्हणजे tree — एक Panel मध्ये Buttons आणि इतर Panels असतात, कितीही nested. तुम्हाला संपूर्ण tree वर render() (किंवा size()) call करायचं आहे 'हे container आहे की leaf?' special-case न करता. Composite pattern objects च्या गटाला एका object सारखं वागवू देतं.

What
काय

Composite: compose objects into tree structures and let clients treat individual objects (leaves) and compositions (branches) uniformly through one interface.

Composite: objects ला tree रचनांत compose करा आणि clients ना वैयक्तिक objects (leaves) आणि compositions (branches) एकसारखे एका interface द्वारे वागवू द्या.

Why
का

It removes "is this one thing or a collection?" branching everywhere. Operations recurse naturally, and the tree can nest to any depth.

ते सगळीकडचं 'ही एक गोष्ट आहे की collection?' branching काढतं. Operations नैसर्गिकपणे recurse होतात, आणि tree कितीही खोल nest होऊ शकतं.

How
कसं

Define one interface (render(), size()); leaves implement it directly; composites implement it by iterating their children and combining results.

एक interface ठरवा (render(), size()); leaves ते थेट राबवतात; composites ते त्यांच्या children वर iterate करून आणि results एकत्र करून राबवतात.

Branch and leaf share one interface
Branch आणि leaf एक interface सामायिक करतात
          «Component: render()»
                 │
        ┌────────┴─────────┐
     Panel (composite)    Button (leaf)
        │  render() = render each child
     ┌──┴───┐
   Button  Panel ──► Button, Input…      call root.render(); it recurses.
   client code never checks "leaf or container?" — same call works on both
🧠
The DOM and React trees are composites: a <div> holds elements and other divs; you render the root and it recurses. A file system is the same — a directory contains files and directories, and du -sh recurses uniformly over both.
🧠
DOM आणि React trees म्हणजे composites: एक <div> मध्ये elements आणि इतर divs असतात; तुम्ही root render करता आणि ते recurse होतं. File system तसंच — directory मध्ये files आणि directories असतात, आणि du -sh दोन्हींवर एकसारखं recurse होतं.
🌲
An org chart: "count reports" works whether you point at an IC (leaf) or a VP (a subtree of managers and ICs). You ask the node; it sums its children. No special case for "manager vs individual."
🌲
Org chart: 'reports मोजा' चालतं मग तुम्ही IC (leaf) कडे बोट दाखवा की VP कडे (managers आणि ICs चं subtree). तुम्ही node ला विचारता; तो त्याची children जोडतो. 'manager vs individual' साठी special case नाही.
Leaves and composites, one interface
interface Component { render(): string }

class Button implements Component {                    // leaf
  constructor(private label: string) {}
  render(){ return `[${this.label}]` }
}
class Panel implements Component {                     // composite
  private children: Component[] = []
  add(c: Component){ this.children.push(c); return this }
  render(){ return '(' + this.children.map(c => c.render()).join(' ') + ')' }  // recurse
}
const ui = new Panel().add(new Button('OK')).add(new Panel().add(new Button('Cancel')))
ui.render()   // works on the whole tree — no "leaf or branch?" checks
🧒
In plain wordsThink of folders on your computer: a folder can hold files and other folders, as deep as you like. When you ask "how big is this?", it doesn't matter if you point at a single file or a folder full of folders — you ask, and it adds everything up. Composite lets your code treat "one thing" and "a group of things" exactly the same way, so nested structures just work.
🧒
सोप्या शब्दांततुमच्या computer वरचे folders विचारा: एका folder मध्ये files आणि इतर folders असू शकतात, कितीही खोल. 'हे किती मोठं आहे?' विचारता तेव्हा तुम्ही एका file कडे दाखवा की folders भरलेल्या folder कडे, फरक पडत नाही — तुम्ही विचारता, आणि ते सगळं जोडतं. Composite तुमच्या code ला 'एक गोष्ट' आणि 'गोष्टींचा गट' अगदी एकसारखे वागवू देतं, म्हणून nested रचना सहज चालतात.
🌍
In the wildThe DOM and React/Vue/Flutter component trees, file systems, UI view hierarchies (Android ViewGroup/View), abstract syntax trees (compilers/linters), org charts, nested menus, scene graphs in games/graphics, and grouped shapes in design tools (group a group). Anytime "a whole" and "a part" respond to the same operations, it's Composite.
🌍
प्रत्यक्ष वापरातDOM आणि React/Vue/Flutter component trees, file systems, UI view hierarchies (Android ViewGroup/View), abstract syntax trees (compilers/linters), org charts, nested menus, games/graphics मधले scene graphs, आणि design tools मधले grouped shapes. जेव्हा 'संपूर्ण' आणि 'भाग' एकाच operations ला प्रतिसाद देतात, तेव्हा तो Composite.

✅ Do

  • Use it for genuine part-whole tree hierarchies
  • Put the recursive operation on the interface so branches and leaves share it
  • Let composites combine their children's results

✅ हे करा

  • खऱ्या part-whole tree hierarchies साठी वापरा
  • recursive operation सामायिक interface वर ठेवा म्हणजे branches आणि leaves ती सामायिक करतात
  • composites ना त्यांच्या children चे results एकत्र करू द्या

❌ Don't

  • Force leaf objects to implement child-management methods (add/remove) they can't support
  • Use it where the data isn't actually tree-shaped

❌ हे टाळा

  • leaf objects ना child-management methods (add/remove) राबवायला भाग पाडू नका ज्या ते समर्थत नाहीत
  • data खरंच tree-आकाराचा नसेल तिथे वापरू नका
Gotcha: the design tension is where to put add()/remove(). Put child-management on the shared Component interface and every leaf is forced to implement (or throw on) methods it can't support — an LSP smell (see OOP topic 15). Put it only on Composite and clients must type-check to add children — losing some uniformity. There's no free lunch; pick based on whether callers need to treat everything as a potential container. Also watch recursion on very deep or cyclic trees — guard against stack overflows and cycles.
अडचण: design tension म्हणजे add()/remove() कुठे ठेवायचं. child-management सामायिक Component interface वर ठेवा आणि प्रत्येक leaf ला ती न-राबवता येणारी methods राबवायला (किंवा throw करायला) भाग पडतं — एक LSP smell (OOP topic 15). फक्त Composite वर ठेवा आणि clients ना children जोडायला type-check करावं लागतं. फुकट काही नाही; callers ना सगळं संभाव्य container म्हणून वागवायचं आहे का त्यानुसार निवडा. खूप खोल किंवा cyclic trees वर recursion ही सांभाळा.
13Bridge — vary two dimensions independentlyBridge — दोन आयाम स्वतंत्रपणे बदला

Scenario: you send notifications of different kinds (alert, reminder, receipt) over different channels (email, SMS, push). If you subclass one per combination you get 3×3 = 9 classes, and adding a channel means adding three more. Bridge splits the two axes — Notification (kind) and Channel (delivery) — so they vary independently: 3 + 3 classes, not 9.

परिस्थिती: तुम्ही वेगवेगळ्या प्रकारच्या (alert, reminder, receipt) notifications वेगवेगळ्या channels (email, SMS, push) वर पाठवता. प्रति संयोग एक subclass केलं तर 3×3 = 9 classes, आणि channel जोडणं म्हणजे आणखी तीन. Bridge दोन अक्ष वेगळे करतं — Notification (प्रकार) आणि Channel (delivery) — म्हणून ते स्वतंत्रपणे बदलतात: 3 + 3 classes, 9 नाही.

What
काय

Bridge: separate an abstraction from its implementation so the two can vary independently, connecting them by composition instead of a combinatorial class hierarchy.

Bridge: एका abstraction ला त्याच्या implementation पासून वेगळं करा म्हणजे दोन्ही स्वतंत्रपणे बदलू शकतात, त्यांना combinatorial class hierarchy ऐवजी composition ने जोडून.

Why
का

It prevents the M×N subclass explosion when two dimensions both change. Add a kind or a channel and it's +1 class, not +N.

दोन आयाम दोन्ही बदलतात तेव्हा ते M×N subclass स्फोट रोखतं. एक प्रकार किंवा channel जोडा आणि तो +1 class, +N नाही.

How
कसं

The abstraction holds a reference to an implementor interface and delegates the varying part to it. Compose a kind with a channel at runtime.

abstraction एका implementor interface चा reference धरतं आणि बदलणारा भाग त्याला delegate करतं. प्रकार आणि channel runtime ला compose करा.

Two axes, composed — not multiplied
दोन अक्ष, composed — गुणाकार नाही
  ❌ M×N subclasses:                 ✅ bridge (M + N):
   AlertEmail  AlertSms  AlertPush    «Notification» ──has-a──► «Channel»
   ReminderEmail ReminderSms …           Alert                    EmailChannel
   ReceiptEmail  …  (9, then 12…)         Reminder                 SmsChannel
                                          Receipt                  PushChannel
   add a channel → +3 classes         add a channel → +1 class. axes are independent.
🧠
Device drivers bridge OS and hardware: the OS's "printing" abstraction is separate from each printer's driver (implementor). Add a printer → add a driver, not a new OS print subclass. JDBC is the same: the API (abstraction) is separate from each database driver (implementation).
🧠
Device drivers OS आणि hardware ला पूल घालतात: OS चं 'printing' abstraction प्रत्येक printer च्या driver (implementor) पासून वेगळं. Printer जोडा → driver जोडा, नवीन OS print subclass नाही. JDBC तसंच: API (abstraction) प्रत्येक database driver (implementation) पासून वेगळं.
🔀
Cross-platform rendering: a shape/UI abstraction vs the rendering backend (Canvas, SVG, OpenGL). The remote control (abstraction) works with any TV brand (implementation) because they're bridged, not fused.
🔀
Cross-platform rendering: एक shape/UI abstraction vs rendering backend (Canvas, SVG, OpenGL). Remote (abstraction) कुठल्याही TV brand (implementation) सोबत चालतं कारण ते bridged आहेत, fused नाही.
Notification kind × delivery channel, composed
interface Channel { deliver(to: string, text: string): Promise<void> }   // implementor
class EmailChannel implements Channel { async deliver(to,t){ /* SES */ } }
class SmsChannel   implements Channel { async deliver(to,t){ /* Twilio */ } }

abstract class Notification {                        // abstraction, holds a Channel
  constructor(protected channel: Channel) {}          // the "bridge"
  abstract send(user: User): Promise<void>
}
class Alert extends Notification {
  async send(user: User){ await this.channel.deliver(user.contact, '⚠ Alert: …') }
}
class Receipt extends Notification {
  async send(user: User){ await this.channel.deliver(user.contact, 'Your receipt: …') }
}
// compose freely: new Alert(new SmsChannel());  new Receipt(new EmailChannel())
// add a channel → one class; add a notification kind → one class. no M×N.
🧒
In plain wordsWhen two things both change — what you're sending and how you're sending it — don't make a separate class for every pairing (that's a huge grid). Instead, keep the "what" and the "how" as two separate dials and snap them together. Then adding a new "how" (say, WhatsApp) is one new piece that works with every "what," instead of one new piece per combination.
🧒
सोप्या शब्दांतजेव्हा दोन गोष्टी दोन्ही बदलतात — तुम्ही काय पाठवताय आणि कसं पाठवताय — तेव्हा प्रत्येक जोडीसाठी वेगळी class बनवू नका (तो प्रचंड grid). त्याऐवजी 'काय' आणि 'कसं' दोन वेगळ्या कळा ठेवा आणि त्यांना एकत्र snap करा. मग नवीन 'कसं' (WhatsApp) जोडणं म्हणजे एक नवीन तुकडा जो प्रत्येक 'काय' सोबत चालतो, प्रति संयोग एक तुकडा नाही.
🌍
In the wildJDBC/ODBC (API bridged to DB drivers), device driver models, cross-platform GUI/rendering backends (an app UI vs Canvas/SVG/GPU), notification systems (message type × channel), payment (transaction type × gateway), and persistence abstractions over multiple stores. Whenever you catch an M×N subclass grid forming, Bridge collapses it to M+N.
🌍
प्रत्यक्ष वापरातJDBC/ODBC (API DB drivers ला bridged), device driver models, cross-platform GUI/rendering backends (app UI vs Canvas/SVG/GPU), notification systems (message type × channel), payment (transaction type × gateway), आणि अनेक stores वरचे persistence abstractions. जेव्हा M×N subclass grid तयार होताना दिसतो, तेव्हा Bridge तो M+N वर आणतं.

✅ Do

  • Use it when two dimensions genuinely vary independently
  • Connect abstraction and implementor by composition, injected at runtime
  • Reach for it when you see an M×N subclass explosion forming

✅ हे करा

  • दोन आयाम खरोखर स्वतंत्रपणे बदलतात तेव्हा वापरा
  • abstraction आणि implementor ला composition ने जोडा, runtime ला injected
  • M×N subclass स्फोट तयार होताना दिसतो तेव्हा घ्या

❌ Don't

  • Introduce a bridge for one dimension that varies (that's over-engineering)
  • Confuse it with Adapter — Bridge is designed up front; Adapter retrofits an existing mismatch

❌ हे टाळा

  • एकच बदलणाऱ्या आयामासाठी bridge आणू नका (ते over-engineering)
  • Adapter शी गोंधळू नका — Bridge आधीच design केलेला; Adapter अस्तित्वात असलेली विसंगती retrofit करतो
Gotcha: Bridge is easy to confuse with Strategy and Adapter because all three use composition. The distinction: Strategy (topic 15) swaps one algorithm behind an interface; Bridge separates two independently-varying dimensions (abstraction and implementation); Adapter (topic 8) retrofits a mismatch between existing interfaces you didn't design together. Bridge is a deliberate, up-front split for anticipated two-axis variation — applying it to a single axis, or before you actually have both dimensions varying, just adds indirection you don't need yet (topic 28).
अडचण: Bridge Strategy आणि Adapter शी गोंधळवणं सोपं कारण तिघे composition वापरतात. फरक: Strategy (topic 15) एका step साठी algorithm बदलतो; Bridge दोन स्वतंत्रपणे-बदलणारे आयाम वेगळे करतो; Adapter (topic 8) तुम्ही एकत्र design न केलेल्या interfaces मधली विसंगती retrofit करतो. Bridge हा अपेक्षित दोन-अक्ष variation साठी मुद्दाम, आधीच केलेला split.
14Flyweight — share to save memoryFlyweight — memory वाचवायला सामायिक करा

Scenario: a text editor rendering a million characters. Giving every character its own object with font, size, and glyph metrics would blow up memory. The Flyweight pattern shares the intrinsic state (the glyph "A" at 12pt Arial is one shared object) and passes the extrinsic state (its position on the page) from outside — so a million characters reuse a few dozen glyph objects.

परिस्थिती: एक लाख अक्षरं render करणारा text editor. प्रत्येक अक्षराला font, size, glyph metrics असलेला स्वतःचा object दिल्यास memory फुटेल. Flyweight pattern intrinsic state सामायिक करतं (12pt Arial मधलं glyph "A" म्हणजे एक सामायिक object) आणि extrinsic state (पानावरची त्याची जागा) बाहेरून पास करतं — म्हणून एक लाख अक्षरं काही डझन glyph objects पुनर्वापरतात.

What
काय

Flyweight: minimize memory by sharing the common, immutable part (intrinsic state) of many similar objects, and supplying the variable part (extrinsic state) externally per use.

Flyweight: अनेक सारख्या objects चा सामायिक, immutable भाग (intrinsic state) सामायिक करून आणि बदलता भाग (extrinsic state) प्रति-वापर बाहेरून देऊन memory कमी करा.

Why
का

When you have huge numbers of similar objects, sharing the identical portion turns millions of objects into a small pool plus lightweight references.

तुमच्याकडे प्रचंड संख्येने सारखे objects असतात, तेव्हा एकसारखा भाग सामायिक करणं लाखो objects ना एका लहान pool + हलक्या references मध्ये बदलतं.

How
कसं

Split state into intrinsic (shared, cached in a factory) and extrinsic (passed in by the caller). A factory returns the shared flyweight for a given key.

state intrinsic (सामायिक, factory मध्ये cached) आणि extrinsic (caller ने पास केलेलं) मध्ये विभागा. एक factory दिलेल्या key साठी सामायिक flyweight परत करतो.

Share the identical part; pass the rest in
एकसारखा भाग सामायिक करा; बाकी पास करा
   1,000,000 characters on screen
        │  each needs: glyph + font (intrinsic)  +  x,y position (extrinsic)
        ▼
   Glyph('A',12pt,Arial)  ← ONE shared object, reused everywhere 'A' appears
        used at (0,0), (14,0), (28,0)…  ← positions passed in, not stored per-char
   memory: ~dozens of glyph objects, not a million
🧠
String interning is flyweight: Java/Python/C# keep one shared instance of identical string literals instead of duplicating them. Browsers cache one glyph per character/font and stamp it at many positions. Same principle: store the identical thing once.
🧠
String interning म्हणजे flyweight: Java/Python/C# एकसारख्या string literals ची एक सामायिक instance ठेवतात, नक्कल न करता. Browsers प्रति अक्षर/font एक glyph cache करतात आणि अनेक जागी stamp करतात. तोच तत्त्व: एकसारखी गोष्ट एकदा साठवा.
🎮
Game tile/particle sharing: a forest of 10,000 trees shares a handful of tree models/textures (intrinsic); each instance only stores its position and scale (extrinsic). That's how huge scenes fit in memory.
🎮
Game tile/particle sharing: 10,000 झाडांचं जंगल काही tree models/textures (intrinsic) सामायिक करतं; प्रत्येक instance फक्त त्याची जागा आणि scale (extrinsic) साठवतो. म्हणूनच प्रचंड दृश्यं memory मध्ये बसतात.
A glyph flyweight factory shares intrinsic state
class Glyph {                                 // intrinsic (shared, immutable) state
  constructor(readonly char: string, readonly font: string, readonly size: number) {}
  drawAt(x: number, y: number){ /* render this glyph at an EXTRINSIC position */ }
}
class GlyphFactory {
  private pool = new Map<string, Glyph>()
  get(char: string, font: string, size: number): Glyph {
    const key = `${char}|${font}|${size}`
    let g = this.pool.get(key)
    if (!g){ g = new Glyph(char, font, size); this.pool.set(key, g) }  // create once
    return g                                                            // reuse forever
  }
}
// a million 'A's → one Glyph('A',...) object; positions passed to drawAt(x,y)
🧒
In plain wordsIf you need a zillion things that are mostly the same, don't store a full copy of each. Keep one master copy of the shared part and just remember the little differences (like where each one goes). Like a rubber stamp: you have one stamp for the letter "A" and press it wherever you need an A — you don't carve a brand-new "A" a million times.
🧒
सोप्या शब्दांततुम्हाला बहुतांश सारख्या असलेल्या असंख्य गोष्टी लागत असतील, तर प्रत्येकाची पूर्ण प्रत साठवू नका. सामायिक भागाची एक master प्रत ठेवा आणि फक्त छोटे फरक (प्रत्येक कुठे जातो) लक्षात ठेवा. रबरी stamp सारखं: 'A' अक्षरासाठी एक stamp असतो आणि जिथे A हवा तिथे दाबता — तुम्ही लाख वेळा नवीन 'A' कोरत नाही.
🌍
In the wildString interning (Java String.intern(), Python small-int/string caching, JS Symbol), browser glyph/font caches and text rendering, game engines sharing meshes/textures/tiles, immutable shared config, Integer.valueOf's cached small integers, and icon/sprite atlases. Anywhere "millions of nearly-identical objects" would otherwise blow memory, flyweight sharing appears.
🌍
प्रत्यक्ष वापरातString interning (Java String.intern(), Python small-int/string caching, JS Symbol), browser glyph/font caches आणि text rendering, meshes/textures/tiles सामायिक करणारे game engines, immutable सामायिक config, Integer.valueOf ची cached small integers, आणि icon/sprite atlases. जिथे 'लाखो जवळपास-एकसारखे objects' memory फोडतील, तिथे flyweight sharing दिसतं.

✅ Do

  • Use it when object count is huge and much of the state is shared & immutable
  • Cleanly separate intrinsic (shared) from extrinsic (per-use) state
  • Keep flyweights immutable so sharing is safe

✅ हे करा

  • object count प्रचंड आणि बरंचसं state सामायिक आणि immutable असेल तेव्हा वापरा
  • intrinsic (सामायिक) आणि extrinsic (प्रति-वापर) state स्वच्छपणे वेगळं करा
  • flyweights immutable ठेवा म्हणजे sharing सुरक्षित

❌ Don't

  • Apply it when you have few objects (the factory/complexity isn't worth it)
  • Store mutable per-instance data in a shared flyweight (corrupts every user)

❌ हे टाळा

  • थोडे objects असताना लावू नका (factory/गुंतागुंत योग्य नाही)
  • सामायिक flyweight मध्ये mutable प्रति-instance data साठवू नका (प्रत्येक user दूषित होतो)
Gotcha: flyweights must be immutable — since one object is shared by thousands of users, any mutable field would let one caller's change corrupt everyone else's. The whole pattern hinges on the shared (intrinsic) state being read-only. Also, it's a memory optimization with real complexity cost (a factory, split state, extrinsic threading through call sites) — only worth it when profiling shows object count is genuinely the problem. Modern GC and value types handle most cases; reach for Flyweight for the extreme end (millions of objects), not routinely.
अडचण: flyweights immutable असलेच पाहिजेत — एक object हजारो users सामायिक करत असल्याने, कुठलंही mutable field एका caller च्या बदलाला इतर सगळ्यांचं दूषित करू देईल. संपूर्ण pattern सामायिक (intrinsic) state read-only असण्यावर टिकतो. आणि हे खऱ्या गुंतागुंतीच्या खर्चासह memory optimization आहे — profiling ने object count खरंच समस्या दाखवली तरच घ्या. Modern GC आणि value types बहुतेक cases हाताळतात.
Part III · Behavioral — how objects interact
भाग III · Behavioral — objects कसे संवाद करतात
15Strategy — swap the algorithm at runtimeStrategy — runtime ला algorithm बदला★ core★ गाभा

Scenario: shipping cost is computed differently per carrier (flat, weight-based, zone-based). Baking all three into one method with a switch means editing it for every new carrier. Strategy makes each algorithm a separate interchangeable object, injected into the context: new Checkout(new WeightBasedShipping()).

परिस्थिती: shipping खर्च प्रति carrier वेगळा मोजला जातो (flat, weight-based, zone-based). तिन्ही एका method मध्ये switch ने कोंबणं म्हणजे प्रत्येक नव्या carrier साठी ती edit करणं. Strategy प्रत्येक algorithm ला वेगळा interchangeable object करतं, context मध्ये injected: new Checkout(new WeightBasedShipping()).

What
काय

Strategy: define a family of interchangeable algorithms behind one interface and let the client pick/swap which one to use at runtime.

Strategy: एका interface मागे interchangeable algorithms चं कुटुंब ठरवा आणि client ला runtime ला कोणता वापरायचा/बदलायचा ते निवडू द्या.

Why
का

It removes algorithm-selection if/switch ladders, makes each algorithm independently testable, and lets you add a new one without touching the others (Open/Closed).

ते algorithm-निवडीच्या if/switch शिड्या काढतं, प्रत्येक algorithm स्वतंत्रपणे testable करतं, आणि इतरांना हात न लावता नवीन जोडू देतं (Open/Closed).

How
कसं

Define a strategy interface; implement one class per algorithm; the "context" holds a strategy and delegates the varying step to it. Inject the choice.

एक strategy interface ठरवा; प्रति algorithm एक class राबवा; 'context' एक strategy धरतो आणि बदलणारा step त्याला delegate करतो. निवड inject करा.

One interface, interchangeable algorithms
एक interface, interchangeable algorithms
   Checkout(context) ──► «ShippingStrategy.cost(order)»
                                 ▲          ▲          ▲
                          FlatRate   WeightBased   ZoneBased
   swap at runtime: checkout.setStrategy(new ZoneBased())
   add a carrier → new class implementing the interface. context unchanged.
🧠
A sort comparator is a strategy: arr.sort(byPrice) vs arr.sort(byName) — same sort algorithm, pluggable comparison function. In languages with first-class functions, a strategy is often just a function you pass in.
🧠
Sort comparator म्हणजे strategy: arr.sort(byPrice) vs arr.sort(byName) — तोच sort algorithm, pluggable comparison function. first-class functions असलेल्या languages मध्ये strategy अनेकदा फक्त एक function जो तुम्ही पास करता.
🔧
Passport.js auth strategies, compression algorithm selection (gzip vs brotli), and pluggable pricing/tax/discount rules are all Strategy — the surrounding flow is fixed; one decision step is swappable.
🔧
Passport.js auth strategies, compression algorithm निवड (gzip vs brotli), आणि pluggable pricing/tax/discount नियम सगळे Strategy — भोवतीचा flow निश्चित; एक निर्णय-step swappable.
Shipping strategies, injected into the context
interface ShippingStrategy { cost(order: Order): Money }
class FlatRate     implements ShippingStrategy { cost(o){ return Money.of(5,'USD') } }
class WeightBased  implements ShippingStrategy { cost(o){ return Money.of(o.weightKg*2,'USD') } }
class ZoneBased    implements ShippingStrategy { cost(o){ /* by destination */ } }

class Checkout {
  constructor(private shipping: ShippingStrategy) {}   // the strategy is injected
  quote(order: Order){ return order.total().add(this.shipping.cost(order)) }
}
new Checkout(new WeightBased()).quote(order)     // pick per carrier at runtime
// in JS you'd often just pass a function: (order) => Money.of(order.weightKg*2,'USD')
🧒
In plain wordsWhen there are several ways to do one step — like several ways to calculate shipping — make each way its own little swappable module and plug in whichever you want. The rest of the checkout doesn't change; you just hand it a different "how to calculate shipping" piece. Adding a new way is adding a new piece, not rewiring the machine.
🧒
सोप्या शब्दांतएका step ला अनेक मार्ग असतील — shipping मोजायचे अनेक मार्ग — तर प्रत्येक मार्ग स्वतःचा swappable module करा आणि हवा तो plug in करा. बाकीचं checkout बदलत नाही; तुम्ही फक्त वेगळा 'shipping कसं मोजायचं' तुकडा देता. नवीन मार्ग जोडणं म्हणजे नवीन तुकडा जोडणं, यंत्र पुन्हा-wiring करणं नाही.
🌍
In the wildSort comparators (Comparator, sort(key=…)), Passport.js auth strategies, pricing/tax/discount/shipping rule engines, compression/encryption algorithm selection, retry/backoff policies, and ML model selection. In functional languages Strategy collapses into "pass a function," which is why higher-order functions are said to subsume it.
🌍
प्रत्यक्ष वापरातSort comparators (Comparator, sort(key=…)), Passport.js auth strategies, pricing/tax/discount/shipping rule engines, compression/encryption algorithm निवड, retry/backoff policies, आणि ML model निवड. functional languages मध्ये Strategy 'एक function पास करा' मध्ये कोसळतं, म्हणून higher-order functions ते सामावतात.

✅ Do

  • Use it when one step has several interchangeable algorithms
  • Inject the strategy so it's swappable and testable
  • In FP-friendly languages, pass a function instead of a whole class

✅ हे करा

  • एका step ला अनेक interchangeable algorithms असतील तेव्हा वापरा
  • strategy inject करा म्हणजे ती swappable आणि testable
  • FP-मैत्रीपूर्ण languages मध्ये पूर्ण class ऐवजी function पास करा

❌ Don't

  • Create a strategy hierarchy for a single algorithm that never varies
  • Re-introduce a switch to pick the strategy in the context itself

❌ हे टाळा

  • कधीच न बदलणाऱ्या एकाच algorithm साठी strategy hierarchy बनवू नका
  • strategy निवडायला context मध्येच switch परत आणू नका
Gotcha: Strategy, Bridge, and State look alike (all delegate to a composed object). Strategy is about interchangeable algorithms for one step, chosen by the client; State (topic 19) is about an object changing its own behavior as its state changes (and often switching itself); Bridge (topic 13) separates two independent dimensions. Also: in a language with first-class functions, a full Strategy class per algorithm is usually overkill — pass a lambda. Reserve the class form for strategies that carry configuration/state or need naming.
अडचण: Strategy, Bridge, आणि State सारखे दिसतात (सगळे composed object ला delegate करतात). Strategy म्हणजे एका step साठी interchangeable algorithms, client ने निवडलेले; State (topic 19) म्हणजे object त्याचं state बदलताच स्वतःचं behavior बदलणं; Bridge (topic 13) दोन स्वतंत्र आयाम वेगळे करतो. आणि: first-class functions असलेल्या language मध्ये प्रति algorithm पूर्ण Strategy class अनेकदा जास्तच — lambda पास करा.
16Observer — publish changes to many listenersObserver — बदल अनेक listeners ना publish करा★ everywhere★ सर्वत्र

Scenario: when an order ships, several things must react — email the customer, update analytics, notify the warehouse — and you'll keep adding reactions. Hard-coding all of them into ship() couples it to everything. Observer lets the order publish an event and any number of subscribers react, without the publisher knowing who's listening.

परिस्थिती: order ship होताच अनेक गोष्टींनी प्रतिक्रिया द्यावी — customer ला email, analytics update, warehouse ला सूचना — आणि तुम्ही आणखी प्रतिक्रिया जोडत राहाल. त्या सगळ्या ship() मध्ये hardcode करणं त्याला सगळ्याशी जोडतं. Observer order ला एक event publish करू देतं आणि कितीही subscribers प्रतिक्रिया देतात, publisher ला कोण ऐकतंय हे न कळता.

What
काय

Observer: a subject maintains a list of observers and notifies them automatically when its state changes, so many objects react to one event without tight coupling.

Observer: एक subject observers ची यादी ठेवतो आणि त्याचं state बदलताच त्यांना आपोआप सूचना देतो, म्हणून अनेक objects घट्ट coupling शिवाय एका event ला प्रतिक्रिया देतात.

Why
का

The publisher stays decoupled from reacters — add/remove listeners freely. It's the backbone of event-driven UIs and reactive systems.

publisher प्रतिक्रिया देणाऱ्यांपासून decoupled राहतो — listeners मोकळेपणी जोडा/काढा. Event-driven UIs आणि reactive systems चा कणा.

How
कसं

Subject exposes subscribe/unsubscribe; on change it iterates observers and calls their handler. Observers register interest and react.

Subject subscribe/unsubscribe उघड करतो; बदलावर तो observers वर iterate करून त्यांचा handler call करतो. Observers रस register करतात आणि प्रतिक्रिया देतात.

One event, many independent reacters
एक event, अनेक स्वतंत्र प्रतिक्रिया देणारे
   Order.ship()  ──emit('shipped')──►  observers notified:
                                          ├─ EmailObserver.on(e)   → send email
                                          ├─ AnalyticsObserver.on(e)→ track
                                          └─ WarehouseObserver.on(e)→ update stock
   the Order doesn't know who's listening. add/remove observers without touching it.
🧠
DOM events are Observer: element.addEventListener('click', handler) — many handlers subscribe; the element notifies them all on click. Node's EventEmitter, RxJS Observables, and Redux store.subscribe are the same subscribe-and-get-notified shape.
🧠
DOM events म्हणजे Observer: element.addEventListener('click', handler) — अनेक handlers subscribe करतात; element त्या सगळ्यांना click वर सूचना देतो. Node चं EventEmitter, RxJS Observables, आणि Redux store.subscribe तोच subscribe-आणि-सूचना आकार.
📰
A newsletter: subscribers register; when an issue drops, everyone gets it. The publisher doesn't tailor sends or know each subscriber — it just notifies the list. Unsubscribe and you stop hearing about it.
📰
Newsletter: subscribers register करतात; issue आली की सगळ्यांना मिळते. Publisher प्रत्येक subscriber ला ओळखत नाही — फक्त यादीला सूचना देतो. Unsubscribe करा आणि ऐकणं थांबतं.
A subject that notifies its observers
type Handler<T> = (event: T) => void
class Subject<T> {
  private observers = new Set<Handler<T>>()
  subscribe(h: Handler<T>){ this.observers.add(h); return () => this.observers.delete(h) } // returns unsubscribe
  notify(event: T){ this.observers.forEach(h => h(event)) }
}
const shipped = new Subject<Order>()
shipped.subscribe(o => mailer.sendShipped(o))       // add reactions freely,
shipped.subscribe(o => analytics.track('shipped', o)) // without editing the publisher
// on ship: shipped.notify(order)
🧒
In plain wordsOne thing happens (an order ships) and lots of others want to know. Instead of the order personally telling each one, it just shouts "shipped!" and anyone who signed up to listen reacts on their own. You can add new listeners (or remove them) anytime, and the order never has to change — it doesn't even know who's listening.
🧒
सोप्या शब्दांतएक गोष्ट घडते (order ship होते) आणि अनेकांना कळायचं असतं. प्रत्येकाला वैयक्तिकपणे सांगण्याऐवजी, order फक्त 'shipped!' ओरडते आणि ऐकायला सही केलेला कुणीही स्वतःहून प्रतिक्रिया देतो — billing, shipping, email. तुम्ही कधीही नवीन listeners जोडू (किंवा काढू) शकता, आणि order कधीच बदलत नाही — तिला कोण ऐकतंय हे माहीतही नसतं.
🌍
In the wildDOM addEventListener, Node EventEmitter, RxJS/Reactive Extensions, Redux/Vuex subscribe, React state + useEffect (components observe state), MobX/Vue reactivity, model→view notifications in MVC, and database change streams / websockets. It's the in-process cousin of Pub/Sub (topic 26), which distributes the same idea across services.
🌍
प्रत्यक्ष वापरातDOM addEventListener, Node EventEmitter, RxJS/Reactive Extensions, Redux/Vuex subscribe, React state + useEffect, MobX/Vue reactivity, MVC मधली model→view सूचना, आणि database change streams / websockets. हे Pub/Sub (topic 26) चा in-process चुलतभाऊ, जो तीच कल्पना services ओलांडून वितरतो.

✅ Do

  • Use it when many objects must react to one object's changes
  • Return/track an unsubscribe handle to avoid leaks
  • Keep observer handlers small and side-effect-focused

✅ हे करा

  • अनेक objects एका object च्या बदलांना प्रतिक्रिया द्यावी लागते तेव्हा वापरा
  • leaks टाळायला unsubscribe handle परत/track करा
  • observer handlers लहान आणि side-effect-केंद्रित ठेवा

❌ Don't

  • Forget to unsubscribe (the #1 memory-leak source in UIs)
  • Build long chains where observers trigger observers trigger observers (hard to trace)

❌ हे टाळा

  • unsubscribe विसरू नका (UIs मधला #1 memory-leak स्रोत)
  • observers observers trigger करणाऱ्या लांब साखळ्या बांधू नका (अनुसरायला कठीण)
Gotcha: the two classic bugs are leaks and invisible cascades. Forgetting to unsubscribe keeps dead objects alive (React's useEffect cleanup exists precisely for this); and when observers themselves emit events that trigger more observers, you get cascading updates that are hard to trace and can even loop infinitely. Notification order is also usually undefined — don't rely on observer A running before B. For cross-service fan-out, don't hand-roll Observer; use a real Pub/Sub broker (topic 26) with delivery guarantees.
अडचण: दोन classic bugs म्हणजे leaks आणि अदृश्य cascades. Unsubscribe विसरणं मृत objects जिवंत ठेवतं (React चं useEffect cleanup नेमकं यासाठी); आणि observers स्वतः events emit करून आणखी observers trigger करतात, तेव्हा अनुसरायला कठीण, अगदी अनंत लूप होणारे cascading updates मिळतात. सूचनेचा क्रम ही सहसा अपरिभाषित. Cross-service fan-out साठी Observer हाताने बांधू नका; delivery guarantees असलेला खरा Pub/Sub broker (topic 26) वापरा.
17Command — actions as objectsCommand — क्रिया objects म्हणून★ powerful★ शक्तिशाली

Scenario: you need undo/redo in an editor, and a background job queue where work runs later on another machine. Both need the same thing: an action turned into an object you can store, pass around, queue, log, and replay. That's the Command pattern — encapsulate "do this" (and often "undo this") as a first-class object.

परिस्थिती: तुम्हाला editor मध्ये undo/redo हवं, आणि background job queue जिथे काम नंतर दुसऱ्या machine वर चालतं. दोघांना तीच गोष्ट लागते: object मध्ये बदललेली क्रिया जी तुम्ही साठवू, पास करू, queue करू, log करू, आणि replay करू शकता. हाच Command pattern — 'हे कर' (आणि अनेकदा 'हे undo कर') एक first-class object म्हणून encapsulate करा.

What
काय

Command: wrap a request/action as an object with an execute() (and often undo()), decoupling the thing that invokes an action from the thing that performs it.

Command: एका request/क्रियेला execute() (आणि अनेकदा undo()) असलेला object म्हणून गुंडाळा, क्रिया invoke करणारं आणि ती करणारं decouple करून.

Why
का

Actions become storable and manipulable: queue them, log them, schedule them, retry them, and undo them. It's the basis of job queues, undo stacks, and CQRS commands.

क्रिया साठवण्याजोग्या आणि हाताळण्याजोग्या होतात: त्यांना queue, log, schedule, retry, आणि undo करा. Job queues, undo stacks, आणि CQRS commands चा पाया.

How
कसं

Each action is a class with execute() capturing its parameters/receiver. An invoker runs (or queues/logs) commands without knowing their concrete type.

प्रत्येक क्रिया एक class जिच्या execute() मध्ये तिचे parameters/receiver असतात. एक invoker commands चालवतो (किंवा queue/log करतो) त्यांचा concrete type न जाणता.

Package an action so you can queue/log/undo it
क्रिया अशी packet करा की queue/log/undo करता येईल
   «Command { execute()  undo() }»
        ├─ ChargeCard(order, amount)     ─► queue → run on a worker later
        ├─ AddItem(cart, product)        ─► push on undo-stack; undo() reverses it
        └─ SendEmail(to, template)       ─► log → retry on failure
   invoker just calls cmd.execute(); it doesn't know WHAT the command does
🧠
Job queues are Command: each enqueued job (Sidekiq, Celery, BullMQ, SQS message) is a serialized action — receiver + params — executed later, elsewhere, with retries. Redux actions are commands too: plain objects describing "what to do."
🧠
Job queues म्हणजे Command: प्रत्येक enqueued job (Sidekiq, Celery, BullMQ, SQS message) एक serialized क्रिया — receiver + params — नंतर, इतरत्र, retries सह चालवली जाते. Redux actions ही commands: 'काय करायचं' वर्णन करणारे साधे objects.
↩️
Undo/redo keeps a stack of command objects; undo() pops and reverses the last one. A restaurant order ticket is a command: it captures the request, gets queued, and the kitchen (receiver) executes it — decoupled from the waiter (invoker).
↩️
Undo/redo command objects चा stack ठेवतं; undo() शेवटचा pop करून उलटतो. रेस्टॉरंट order ticket म्हणजे command: तो request पकडतो, queue होतो, आणि kitchen (receiver) चालवते — waiter (invoker) पासून decoupled.
Commands with execute + undo (undo/redo & queues)
interface Command { execute(): void; undo(): void }

class AddItem implements Command {                      // an action as an object
  constructor(private cart: Cart, private product: Product) {}
  execute(){ this.cart.add(this.product) }
  undo(){ this.cart.remove(this.product) }
}
class History {                                         // invoker + undo stack
  private done: Command[] = []
  run(c: Command){ c.execute(); this.done.push(c) }
  undo(){ this.done.pop()?.undo() }
}
// queue variant: serialize commands and run them on a worker later (job queue)
🧒
In plain wordsInstead of just doing something right now, you write the action down on a card — "add this item," "charge this card" — and hand the card off. Now you can stack the cards to undo them, put them in a to-do line to run later, or keep a copy in case you need to retry. Turning "do X" into a physical thing you can hold is what makes undo, queues, and scheduling possible.
🧒
सोप्या शब्दांतकाही आत्ता करण्याऐवजी, तुम्ही क्रिया एका कार्डावर लिहिता — 'हा item जोड,' 'हे कार्ड charge कर' — आणि कार्ड सोपवता. आता तुम्ही कार्डं रचून undo करू शकता, नंतर चालवायला to-do रांगेत ठेवू शकता, किंवा retry लागल्यास प्रत ठेवू शकता. 'X कर' चं तुम्ही धरू शकणारी physical गोष्ट करणं हेच undo, queues, आणि scheduling शक्य करतं.
🌍
In the wildJob/task queues (Sidekiq, Celery, BullMQ, SQS, Kafka consumers), undo/redo stacks in editors (VS Code, Photoshop), Redux actions & CQRS commands, database transaction logs / write-ahead logs (replayable commands), keybinding→command maps, macro recording, and the Command bus in DDD apps. Serializable commands are what make "run it later, elsewhere, and retry" possible.
🌍
प्रत्यक्ष वापरातJob/task queues (Sidekiq, Celery, BullMQ, SQS, Kafka consumers), editors मधले undo/redo stacks (VS Code, Photoshop), Redux actions आणि CQRS commands, database transaction logs / write-ahead logs (replayable commands), keybinding→command maps, macro recording, आणि DDD apps मधला Command bus. Serializable commands 'नंतर, इतरत्र चालव आणि retry कर' शक्य करतात.

✅ Do

  • Use it for undo/redo, queuing, scheduling, logging, and retry of actions
  • Capture everything the action needs so it can run later/elsewhere
  • Add undo() when you need reversibility

✅ हे करा

  • undo/redo, queuing, scheduling, logging, आणि retry साठी वापरा
  • क्रियेला लागणारं सगळं पकडा म्हणजे ती नंतर/इतरत्र चालेल
  • reversibility लागल्यास undo() जोडा

❌ Don't

  • Wrap a plain immediate function call in a Command for no reason
  • Store un-serializable references in a command you intend to queue/persist

❌ हे टाळा

  • साधा तात्काळ function call विनाकारण Command मध्ये गुंडाळू नका
  • queue/persist करायच्या command मध्ये un-serializable references साठवू नका
Gotcha: undo is deceptively hard. A command that captured the wrong thing can't reverse itself: to undo "delete row," you must have saved the row's prior state (which drifts into the Memento pattern, topic 23). And commands meant to be queued/persisted must be serializable — capturing a live DB connection or a closure over huge objects breaks "run it later on another worker." Design commands to carry plain data (ids, params), resolve receivers at execution time, and store enough state to reverse — or accept that some commands are execute-only.
अडचण: undo फसवं-कठीण आहे. चुकीची गोष्ट पकडलेली command स्वतःला उलटवू शकत नाही: 'row delete कर' undo करायला तुम्ही row चं आधीचं state साठवलेलं हवं (जे Memento pattern कडे झुकतं, topic 23). आणि queue/persist होणाऱ्या commands serializable असलेच पाहिजेत — जिवंत DB connection किंवा प्रचंड objects वर closure पकडणं 'नंतर worker वर चालव' मोडतं. Commands ना plain data (ids, params) वाहायला design करा.
18Chain of Responsibility — pass it down the lineChain of Responsibility — रांगेत पुढे द्या

Scenario: an incoming HTTP request must pass through auth, rate-limiting, logging, and body-parsing before reaching your handler — and each step may pass it on or stop it. Chain of Responsibility links handlers so a request flows down the chain until one handles it (or all get a turn). This is web middleware.

परिस्थिती: येणाऱ्या HTTP request ने तुमच्या handler पर्यंत पोहोचण्याआधी auth, rate-limiting, logging, आणि body-parsing मधून जायला हवं — आणि प्रत्येक step ते पुढे देऊ किंवा थांबवू शकतो. Chain of Responsibility handlers जोडतं म्हणजे request रांगेतून खाली वाहते जोपर्यंत कुणी हाताळत नाही (किंवा सगळ्यांना संधी मिळते). हेच web middleware.

What
काय

Chain of Responsibility: pass a request along a chain of handlers; each decides to handle it, transform it, and/or forward it to the next — decoupling sender from the specific receiver.

Chain of Responsibility: request handlers च्या साखळीवरून पास करा; प्रत्येक ते हाताळायचं, बदलायचं, आणि/किंवा पुढच्याला forward करायचं ठरवतो — sender ला विशिष्ट receiver पासून decouple करून.

Why
का

You can add, remove, or reorder handling steps without touching the others, and no single place needs to know the whole pipeline.

तुम्ही इतरांना हात न लावता handling steps जोडू, काढू, किंवा पुनर्क्रमित करू शकता, आणि एकाही जागेला संपूर्ण pipeline जाणायची गरज नाही.

How
कसं

Each handler holds a reference to the next and calls it (or short-circuits). Assemble the chain in order; feed the request to the head.

प्रत्येक handler next चा reference धरतो आणि तो call करतो (किंवा short-circuit करतो). साखळी क्रमाने जुळवा; request head ला द्या.

Request flows down until handled/stopped
Request हाताळेपर्यंत/थांबेपर्यंत खाली वाहते
   request ─► Auth ─► RateLimit ─► Logging ─► BodyParser ─► Handler
                │         │  (429?     │ next()      │            │
              next()   stop!)        next()        next()      respond
   any link can handle-and-stop or transform-and-forward. reorder freely.
🧠
Express/Koa/Rack middleware and Servlet filters ARE this pattern: each middleware gets (req, res, next) and either responds or calls next() to pass control down the chain. It's the everyday face of Chain of Responsibility.
🧠
Express/Koa/Rack middleware आणि Servlet filters म्हणजेच हा pattern: प्रत्येक middleware ला (req, res, next) मिळतं आणि ते एकतर प्रतिसाद देतं किंवा साखळीत खाली नियंत्रण द्यायला next() call करतं. Chain of Responsibility चा रोजचा चेहरा.
🎫
An approval/escalation chain: a purchase request goes to your manager; if it's over their limit they pass it up to the director, then the VP — each either approves or forwards. Support tiers (L1→L2→L3) work the same way.
🎫
Approval/escalation साखळी: purchase request तुमच्या manager कडे जाते; त्यांच्या मर्यादेवरचं असेल तर director कडे, मग VP कडे — प्रत्येक एकतर मंजूर करतो किंवा पुढे देतो. Support tiers (L1→L2→L3) तसंच.
A middleware-style handler chain
type Ctx = { req: Request; res: Response }
type Handler = (ctx: Ctx, next: () => void) => void

function chain(handlers: Handler[]) {
  return (ctx: Ctx) => {
    let i = -1
    const dispatch = () => { i++; if (i < handlers.length) handlers[i](ctx, dispatch) }
    dispatch()
  }
}
const auth: Handler      = (c, next) => c.req.user ? next() : c.res.status(401)  // stop
const rateLimit: Handler = (c, next) => underLimit(c) ? next() : c.res.status(429)
const app = chain([auth, rateLimit, /* logging, */ handler])  // reorder freely
🧒
In plain wordsLine up a series of checkpoints, and send the request down the line. Each checkpoint can deal with it and stop, or tweak it and wave it on to the next. Nobody needs to know the whole line — each just knows "handle or pass along." That's exactly how a web server runs a request through login-check, rate-limit, logging, and finally your code.
🧒
सोप्या शब्दांतcheckpoints ची मालिका रांगेत लावा, आणि request रांगेतून खाली पाठवा. प्रत्येक checkpoint ते हाताळून थांबवू शकतो, किंवा तपासून पुढच्याला पाठवू शकतो. संपूर्ण रांग कुणाला जाणायची गरज नाही — प्रत्येकाला फक्त 'हाताळ किंवा पुढे दे' माहीत. web server login-तपासणी, rate-limit, logging, आणि शेवटी तुमचा code मधून request अशीच चालवतो.
🌍
In the wildExpress/Koa/Connect middleware, Rack (Ruby), ASP.NET Core middleware, Java Servlet Filters, logging handler levels, DOM event bubbling/capture, exception-handler chains, ETL/processing pipelines, and approval/escalation workflows. Reordering or inserting a step without touching the rest is the everyday payoff.
🌍
प्रत्यक्ष वापरातExpress/Koa/Connect middleware, Rack (Ruby), ASP.NET Core middleware, Java Servlet Filters, logging handler levels, DOM event bubbling/capture, exception-handler साखळ्या, ETL/processing pipelines, आणि approval/escalation workflows. एक step पुनर्क्रमित करणं किंवा घालणं इतरांना हात न लावता हा रोजचा फायदा.

✅ Do

  • Use it for pipelines where steps can handle, transform, or forward
  • Keep each handler single-purpose and order the chain explicitly
  • Allow short-circuiting (a handler that stops the chain)

✅ हे करा

  • steps हाताळू, बदलू, किंवा forward करू शकतात अशा pipelines साठी वापरा
  • प्रत्येक handler एका-हेतूचा ठेवा आणि साखळी स्पष्टपणे क्रमवा
  • short-circuiting ला परवानगी द्या (साखळी थांबवणारा handler)

❌ Don't

  • Build a chain where no handler ever handles the request (it falls off the end silently)
  • Hide critical control flow across so many links it's impossible to follow

❌ हे टाळा

  • कुणीही request हाताळत नाही अशी साखळी बांधू नका (ती शेवटातून silently पडते)
  • इतक्या links मध्ये critical control flow लपवू नका की अनुसरणं अशक्य
Gotcha: two failure modes. First, a request can fall off the end unhandled — if every link forwards and none handles, you get silent no-ops or hangs (in middleware, forgetting to call next() or to respond hangs the request). Second, order is load-bearing and implicit: put logging before auth and you log unauthenticated noise; put body-parsing after the handler and it's too late. Make the chain's order explicit and tested, ensure a terminal handler always exists, and guarantee each link either forwards or completes.
अडचण: दोन अपयश-प्रकार. पहिला, request शेवटातून हाताळल्याशिवाय पडू शकतो — प्रत्येक link forward करतो आणि कुणी हाताळत नाही, तर silent no-ops किंवा hangs (middleware मध्ये next() call करणं किंवा प्रतिसाद देणं विसरणं request hang करतं). दुसरा, क्रम भार-वाहक आणि अंतर्भूत: auth आधी logging ठेवा आणि unauthenticated गोंधळ log होतो. साखळीचा क्रम स्पष्ट आणि tested करा, terminal handler नेहमी असल्याची खात्री करा.
19State — behavior that changes with stateState — state सोबत बदलणारं behavior★ core★ गाभा

Scenario: an Order behaves differently depending on its status — you can pay a pending order, cancel a paid one, but not cancel a shipped one. Encoding this as if (status === …) checks scattered across every method is bug-prone. The State pattern makes each state an object that defines what's allowed and where you can transition.

परिस्थिती: एक Order त्याच्या status नुसार वेगळं वागतो — तुम्ही pending order pay करू शकता, paid cancel करू शकता, पण shipped cancel करू शकत नाही. हे प्रत्येक method मध्ये if (status === …) तपासण्या म्हणून encode करणं bug-प्रवण. State pattern प्रत्येक state ला असा object करतं जो काय परवानगी आहे आणि कुठे transition करता येईल हे ठरवतो.

What
काय

State: let an object alter its behavior when its internal state changes, by delegating to a state object — so it "appears to change class." Each state encapsulates its allowed actions and transitions.

State: object ला त्याचं internal state बदलताच त्याचं behavior बदलू द्या, एका state object ला delegate करून — म्हणून तो 'class बदलल्यासारखा दिसतो.' प्रत्येक state त्याच्या परवानगी असलेल्या क्रिया आणि transitions encapsulate करतो.

Why
का

It replaces sprawling status-conditionals with explicit states, makes illegal transitions impossible, and puts each state's rules in one place — a real state machine.

ते पसरलेल्या status-conditionals ना स्पष्ट states ने बदलतं, बेकायदा transitions अशक्य करतं, आणि प्रत्येक state चे नियम एका ठिकाणी ठेवतं — एक खरं state machine.

How
कसं

Model each state as a class/object implementing the same interface; the context delegates actions to its current state, which may switch the context to a new state.

प्रत्येक state तोच interface राबवणारी class/object म्हणून मॉडेल करा; context क्रिया त्याच्या सध्याच्या state ला delegate करतो, जो context ला नव्या state वर switch करू शकतो.

Each state defines allowed actions + transitions
प्रत्येक state परवानगी असलेल्या क्रिया + transitions ठरवतो
   Pending ──pay()──► Paid ──ship()──► Shipped ──deliver()──► Delivered
      │  cancel() → Cancelled   │ cancel() → refund+Cancelled   │ cancel()? ❌ blocked
   each state OBJECT knows: which actions are legal + where they lead.
   context.cancel() delegates to current state — no scattered status ifs.
🧠
TCP is a state machine: LISTEN → SYN-RCVD → ESTABLISHED → FIN-WAIT… each state permits different packets. A Promise is too: pending → fulfilled/rejected, and .then behaves per state. Workflow engines and game AI use explicit state objects.
🧠
TCP म्हणजे state machine: LISTEN → SYN-RCVD → ESTABLISHED → FIN-WAIT… प्रत्येक state वेगळे packets परवानगी देतो. Promise ही: pending → fulfilled/rejected, आणि .then प्रति state वागतं. Workflow engines आणि game AI स्पष्ट state objects वापरतात.
🚦
A traffic light only transitions Green→Yellow→Red→Green, and each state dictates behavior. Libraries like XState (JS) and Spring StateMachine make these explicit precisely because if-soup state logic is so error-prone.
🚦
Traffic light फक्त Green→Yellow→Red→Green transition करतो, आणि प्रत्येक state behavior ठरवतो. XState (JS) आणि Spring StateMachine सारख्या libraries ती स्पष्ट करतात कारण if-soup state logic इतकं bug-प्रवण.
Order states as objects with explicit transitions
interface OrderState { pay(o: Order): void; cancel(o: Order): void }

class Pending implements OrderState {
  pay(o){ o.setState(new Paid()) }
  cancel(o){ o.setState(new Cancelled()) }
}
class Paid implements OrderState {
  pay(o){ throw new Error('already paid') }
  cancel(o){ o.refund(); o.setState(new Cancelled()) }
}
class Shipped implements OrderState {
  pay(o){ throw new Error('already paid') }
  cancel(o){ throw new Error('cannot cancel a shipped order') }   // illegal transition
}
class Order { constructor(private state: OrderState = new Pending()){}
  setState(s: OrderState){ this.state = s }
  pay(){ this.state.pay(this) }  cancel(){ this.state.cancel(this) } }
🧒
In plain wordsSome things act differently depending on what "mode" they're in — an order that's just been placed can be paid or cancelled, but one that's already shipped can't be cancelled. Instead of scattering "if it's in this mode, then…" checks everywhere, give each mode its own rulebook that says what you can do and what mode you move to next. The order just asks its current mode what to do.
🧒
सोप्या शब्दांतकाही गोष्टी त्या कोणत्या 'mode' मध्ये आहेत त्यानुसार वेगळं वागतात — नुकतीच placed झालेली order pay किंवा cancel होऊ शकते, पण आधीच shipped झालेली cancel होऊ शकत नाही. सगळीकडे 'हे mode असेल तर…' तपासण्या विखुरण्याऐवजी, प्रत्येक mode ला स्वतःचं नियमपुस्तक द्या जे काय करता येईल आणि पुढे कोणत्या mode ला जायचं ते सांगतं. Order फक्त तिच्या सध्याच्या mode ला विचारते.
🌍
In the wildOrder/subscription/payment lifecycles, TCP connection states, JavaScript Promises (pending/fulfilled/rejected), workflow & approval engines, game AI (patrol/chase/flee), CI/CD pipeline stages, document review flows, and dedicated libraries like XState, Spring StateMachine, and AWS Step Functions. Anywhere "what's allowed depends on current status," an explicit state machine beats scattered conditionals.
🌍
प्रत्यक्ष वापरातOrder/subscription/payment lifecycles, TCP connection states, JavaScript Promises (pending/fulfilled/rejected), workflow आणि approval engines, game AI (patrol/chase/flee), CI/CD pipeline stages, document review flows, आणि XState, Spring StateMachine, AWS Step Functions सारख्या समर्पित libraries. जिथे 'काय परवानगी सध्याच्या status वर अवलंबून,' तिथे स्पष्ट state machine विखुरलेल्या conditionals पेक्षा चांगलं.

✅ Do

  • Use it when behavior and allowed actions depend on a lifecycle status
  • Make each state own its legal actions and transitions
  • Reach for a state-machine library when transitions get complex

✅ हे करा

  • behavior आणि परवानगी क्रिया lifecycle status वर अवलंबून असतील तेव्हा वापरा
  • प्रत्येक state ला त्याच्या कायदेशीर क्रिया आणि transitions चा मालक करा
  • transitions जटिल झाल्यास state-machine library घ्या

❌ Don't

  • Scatter if (status === …) checks across many methods
  • Model a boolean flag with two behaviors as a heavyweight state hierarchy

❌ हे टाळा

  • अनेक methods ओलांडून if (status === …) तपासण्या विखरू नका
  • दोन behaviors असलेल्या boolean flag ला जड state hierarchy म्हणून मॉडेल करू नका
Gotcha: State vs Strategy again — structurally identical (delegate to a composed object), different intent. Strategy's object is chosen by the client and doesn't change itself; State's object is switched by the states themselves as the object progresses through a lifecycle. The real-world trap is where transitions live: if each state hard-codes its next states, the transition map is scattered across classes and hard to see whole. For non-trivial machines, keep the transition table explicit (or use a state-machine library) so the full set of legal transitions is visible and testable in one place.
अडचण: State vs Strategy पुन्हा — रचनेने एकसारखे (composed object ला delegate), हेतूने वेगळे. Strategy चा object client निवडतो आणि तो स्वतःला बदलत नाही; State चा object states स्वतःच object lifecycle मधून पुढे जाताना switch करतात. खरा सापळा म्हणजे transitions कुठे राहतात: प्रत्येक state त्याची पुढची states hardcode करत असेल, तर transition-map classes ओलांडून विखुरतो. non-trivial machines साठी transition table स्पष्ट ठेवा.
20Template Method — fixed skeleton, pluggable stepsTemplate Method — निश्चित कणा, pluggable steps

Scenario: every data importer does the same dance — open source, read rows, parse (CSV vs JSON vs XML differs), validate, write to DB, close. The skeleton is identical; only a step or two vary. Template Method defines the skeleton in a base class and lets subclasses fill in specific steps (hook methods).

परिस्थिती: प्रत्येक data importer तेच नृत्य करतो — source उघड, rows वाच, parse (CSV vs JSON vs XML वेगळं), validate, DB मध्ये लिही, बंद कर. कणा एकसारखा; फक्त एक-दोन steps बदलतात. Template Method कणा एका base class मध्ये ठरवतं आणि subclasses ना विशिष्ट steps भरू देतं (hook methods).

What
काय

Template Method: define the skeleton of an algorithm in a method, deferring some steps to subclasses via overridable hook methods — the overall structure is fixed, specific steps vary.

Template Method: एका method मध्ये algorithm चा कणा ठरवा, काही steps overridable hook methods द्वारे subclasses कडे सोपवून — एकंदर रचना निश्चित, विशिष्ट steps बदलतात.

Why
का

It removes duplicated boilerplate around the varying bits and enforces a consistent overall flow (the base controls the sequence; subclasses only fill blanks).

ते बदलणाऱ्या भागांभोवतीचा नक्कल boilerplate काढतं आणि सुसंगत एकंदर flow लागू करतो (base क्रम नियंत्रित करतो; subclasses फक्त blanks भरतात).

How
कसं

A concrete base method calls a sequence of steps, some abstract/overridable. Subclasses override just the steps that differ; they don't touch the sequence.

एक concrete base method steps ची मालिका call करते, काही abstract/overridable. Subclasses फक्त वेगळे steps override करतात; ते मालिका बदलत नाहीत.

Base owns the sequence; subclass fills steps
Base मालिकेचा मालक; subclass steps भरतो
   Importer.run()  (template method — fixed skeleton)
     open()          ← shared
     rows = read()   ← shared
     parse(rows)     ← ABSTRACT hook  ► CsvImporter / JsonImporter override
     validate()      ← shared
     write()         ← shared (or hook)
     close()         ← shared
   subclass changes parse(); it CANNOT reorder or skip the skeleton steps
🧠
Framework lifecycle hooks are template methods: React/Android call their fixed lifecycle and invoke your render()/onCreate(); test frameworks call setUp()/your test/tearDown() in a fixed order. Rails callbacks (before_save) hook into a fixed persistence flow.
🧠
Framework lifecycle hooks म्हणजे template methods: React/Android त्यांचा निश्चित lifecycle call करतात आणि तुमचा render()/onCreate() invoke करतात; test frameworks निश्चित क्रमाने setUp()/तुमचा test/tearDown() call करतात. Rails callbacks (before_save) निश्चित persistence flow मध्ये hook होतात.
📋
A recipe template: "prep → cook → plate" is fixed; the "cook" step differs per dish. You override the step, not the sequence. It's the Hollywood principle — "don't call us, we'll call you" — the framework drives; your hooks get called.
📋
Recipe template: 'prep → cook → plate' निश्चित; 'cook' step प्रति पदार्थ वेगळा. तुम्ही step override करता, मालिका नाही. हे Hollywood तत्त्व — 'तुम्ही आम्हाला call करू नका, आम्ही करू' — framework चालवतो; तुमचे hooks call होतात.
A fixed import skeleton with an overridable parse step
abstract class Importer {
  run(source: string) {                         // the TEMPLATE METHOD (fixed flow)
    const raw = this.open(source)
    const rows = this.parse(raw)                 // ← the varying step (hook)
    const valid = rows.filter(r => this.validate(r))
    this.write(valid)
    this.close()
  }
  protected abstract parse(raw: string): Row[]   // subclasses fill this in
  protected open(s: string){ /* shared */ return read(s) }
  protected validate(r: Row){ return r.id != null }   // shared default (overridable)
  protected write(rows: Row[]){ /* shared */ }
  protected close(){ /* shared */ }
}
class CsvImporter  extends Importer { parse(raw){ return csv(raw) } }   // only the diff
class JsonImporter extends Importer { parse(raw){ return JSON.parse(raw) } }
🧒
In plain wordsLots of jobs follow the same steps in the same order, with just one or two steps done differently. So write the step-by-step plan once, but leave a couple of blanks for the parts that change. Each specific version just fills in the blanks — it can't reshuffle the plan. Like a fill-in-the-blank recipe where only the "which spice" line changes.
🧒
सोप्या शब्दांतबरीच कामं तेच steps त्याच क्रमाने पाळतात, फक्त एक-दोन steps वेगळे. म्हणून step-by-step योजना एकदा लिहा, पण बदलणाऱ्या भागांसाठी काही blanks सोडा. प्रत्येक विशिष्ट आवृत्ती फक्त blanks भरते — ती योजना पुनर्रचित करू शकत नाही. फक्त 'कोणता मसाला' ओळ बदलणाऱ्या fill-in-the-blank recipe सारखं.
🌍
In the wildFramework lifecycle hooks (React/Android/servlet lifecycles), test setUp/tearDown, Rails/Django model callbacks, Spring's JdbcTemplate/RestTemplate (they own the resource dance; you supply the query/mapper), sorting frameworks that call your comparator, and ETL/build-pipeline skeletons. It's the inheritance-based cousin of Strategy (which composes the varying step instead of overriding it).
🌍
प्रत्यक्ष वापरातFramework lifecycle hooks (React/Android/servlet lifecycles), test setUp/tearDown, Rails/Django model callbacks, Spring चं JdbcTemplate/RestTemplate (ते resource-नृत्याचे मालक; तुम्ही query/mapper देता), तुमचा comparator call करणारे sorting frameworks, आणि ETL/build-pipeline कणे. हे Strategy चा inheritance-आधारित चुलतभाऊ.

✅ Do

  • Use it when many variants share one algorithm skeleton with a few varying steps
  • Keep the sequence in the base; expose only the intended hook points
  • Provide sensible default hooks subclasses can optionally override

✅ हे करा

  • अनेक variants एक algorithm कणा + काही बदलणारे steps सामायिक करतात तेव्हा वापरा
  • मालिका base मध्ये ठेवा; फक्त हेतूचे hook points उघड करा
  • subclasses पर्यायाने override करू शकतील असे समजूतदार default hooks द्या

❌ Don't

  • Expose so many hooks that subclasses effectively rewrite the algorithm
  • Reach for inheritance here if composition (Strategy) fits better and is more flexible

❌ हे टाळा

  • इतके hooks उघड करू नका की subclasses प्रभावीपणे algorithm पुन्हा लिहितील
  • composition (Strategy) चांगलं बसत असेल तर इथे inheritance घेऊ नका
Gotcha: Template Method is inheritance-based, so it inherits inheritance's downsides (OOP topics 4, 6): a change to the base skeleton can break every subclass (fragile base class), and a subclass is locked to one parent. Many modern codebases prefer the composition alternative — pass the varying step in as a Strategy/function (e.g. importer(source, parseFn)) — which is more flexible and testable. Use Template Method when the shared flow is genuinely stable and the variation is naturally expressed by subtyping; otherwise inject the step.
अडचण: Template Method inheritance-आधारित आहे, म्हणून त्याला inheritance चे तोटे (OOP topics 4, 6): base कणाच्या बदलाने प्रत्येक subclass मोडू शकतो (fragile base class), आणि subclass एका parent ला बांधलेला. अनेक modern codebases composition पर्याय ला झुकतात — बदलणारा step Strategy/function म्हणून पास करा (उदा. importer(source, parseFn)) — जे अधिक लवचिक आणि testable. सामायिक flow खरोखर स्थिर असेल तेव्हाच Template Method वापरा.
21Iterator — traverse without exposing internalsIterator — internals न उघडता traverse करा

Scenario: you want to loop over a collection — an array, a tree, a paginated API, a database cursor — without caring how it stores its elements. The Iterator pattern provides a uniform way to walk a collection one element at a time, hiding whether it's backed by an array, a linked list, or lazy pages.

परिस्थिती: तुम्हाला एका collection वर loop करायचं आहे — array, tree, paginated API, database cursor — त्याचे elements कसे साठवले याची पर्वा न करता. Iterator pattern collection एक-एक element ने चालायचा एकसमान मार्ग देतो, ती array, linked list, की lazy pages यांनी backed आहे हे लपवून.

What
काय

Iterator: provide a standard way to access elements of a collection sequentially without exposing its underlying representation.

Iterator: collection चे elements क्रमाने access करायचा standard मार्ग द्या, त्याची underlying representation न उघडता.

Why
का

Client code uses one loop shape (for…of) over any collection, and you can iterate lazily (generators, streams, paginated data) without loading everything at once.

Client code कुठल्याही collection वर एक loop-आकार (for…of) वापरतो, आणि तुम्ही सगळं एकदम न load करता lazily iterate करू शकता (generators, streams, paginated data).

How
कसं

Expose an iterator with next() (returning value + done), or implement the language's iterable protocol / a generator, so standard loops work.

next() (value + done परत करणारा) असलेला iterator उघड करा, किंवा language चा iterable protocol / generator राबवा, म्हणजे standard loops चालतात.

One loop shape over any backing structure
कुठल्याही backing रचनेवर एक loop-आकार
   for (const x of collection) { … }
                │  works the same whether collection is:
     ┌──────────┼───────────┬───────────────┬──────────────┐
   Array     LinkedList   Tree (in-order)  DB cursor    Paginated API
   the iterator hides HOW elements are stored/fetched; caller just walks them
🧠
It's built into the language: JS Symbol.iterator + for…of, Python __iter__/generators, Java Iterator/Iterable, C# IEnumerable/yield. Database cursors and Node streams iterate huge/lazy data without loading it all.
🧠
हे language मध्ये built-in आहे: JS Symbol.iterator + for…of, Python __iter__/generators, Java Iterator/Iterable, C# IEnumerable/yield. Database cursors आणि Node streams प्रचंड/lazy data सगळं न load करता iterate करतात.
📼
Generators are lazy iterators: function* yields values on demand — perfect for infinite sequences or streaming a million rows one at a time, so you never hold the whole thing in memory.
📼
Generators म्हणजे lazy iterators: function* मागणीनुसार values yield करतो — infinite sequences किंवा एक लाख rows एक-एक stream करायला योग्य, म्हणून तुम्ही संपूर्ण गोष्ट memory मध्ये कधीच धरत नाही.
A custom iterable + a lazy generator
class Tree {
  constructor(public value: number, public children: Tree[] = []) {}
  *[Symbol.iterator](): Iterator<number> {          // implement the iterable protocol
    yield this.value
    for (const child of this.children) yield* child     // recurse lazily
  }
}
for (const v of new Tree(1, [new Tree(2), new Tree(3)])) console.log(v)  // 1,2,3

// lazy pagination — walk an API without loading every page up front
async function* pages(url: string){
  let next = url
  while (next){ const r = await fetch(next).then(x=>x.json()); yield* r.items; next = r.next }
}
🧒
In plain wordsYou want to go through a bunch of things one by one, without needing to know how they're stored inside. An iterator is like a tour guide that just says "here's the next one… and the next…" until there are no more. The same "give me the next one" works whether it's a simple list, a tree, or pages coming from a website — and it can fetch things only as you ask, instead of all at once.
🧒
सोप्या शब्दांततुम्हाला गोष्टींच्या समूहातून एक-एक जायचं आहे, त्या आत कशा साठवल्या हे न जाणता. Iterator म्हणजे एक tour guide जो फक्त 'हा पुढचा… आणि पुढचा…' म्हणतो जोपर्यंत आणखी नाहीत. तोच 'पुढचा दे' चालतो ती साधी list असो, tree असो, की website कडून येणारी pages असोत — आणि तो सगळं एकदम आणण्याऐवजी तुम्ही विचारताच गोष्टी आणू शकतो.
🌍
In the wildEvery language's for…of/foreach, generators (function*, Python yield), database cursors (stream rows without loading the table), Node/Java streams, paginated API iteration, RxJS/reactive sequences, and tree/graph traversals. Iterator is so fundamental it's a built-in language protocol almost everywhere — you implement it, you don't reinvent it.
🌍
प्रत्यक्ष वापरातप्रत्येक language चं for…of/foreach, generators (function*, Python yield), database cursors (table न load करता rows stream), Node/Java streams, paginated API iteration, RxJS/reactive sequences, आणि tree/graph traversals. Iterator इतकं मूलभूत की ते जवळपास सगळीकडे built-in language protocol.

✅ Do

  • Implement your language's iterable protocol so standard loops work
  • Use lazy iterators/generators for large or streaming data
  • Hide the backing structure behind the iterator

✅ हे करा

  • तुमच्या language चा iterable protocol राबवा म्हणजे standard loops चालतात
  • मोठ्या किंवा streaming data साठी lazy iterators/generators वापरा
  • backing रचना iterator मागे लपवा

❌ Don't

  • Expose internal arrays/nodes for callers to traverse manually
  • Mutate a collection while iterating it (a classic invalidation bug)

❌ हे टाळा

  • callers ना manually traverse करायला internal arrays/nodes उघड करू नका
  • iterate करताना collection mutate करू नका (classic invalidation bug)
Gotcha: modifying a collection while iterating it is the perennial bug — many languages throw ConcurrentModificationException or produce skipped/duplicated elements (JS iterating and splice-ing an array, Python mutating a dict mid-loop). Collect changes and apply them after, or iterate a copy. Second gotcha: lazy iterators are single-pass and stateful — a generator, DB cursor, or stream is consumed once; iterating it again yields nothing. If you need multiple passes, materialize it (to an array) or create a fresh iterator each time.
अडचण: iterate करताना collection बदलणं हा शाश्वत bug — अनेक languages ConcurrentModificationException throw करतात किंवा skipped/duplicated elements देतात (JS array iterate करून splice, Python dict mid-loop बदलणं). बदल जमवून नंतर लावा, किंवा copy iterate करा. दुसरा सापळा: lazy iterators single-pass आणि stateful — generator, DB cursor, किंवा stream एकदा वापरला जातो; पुन्हा iterate केल्यास काहीच मिळत नाही. अनेक passes लागल्यास materialize करा.
22Mediator — centralize many-to-many chatterMediator — अनेक-ते-अनेक बडबड केंद्रित करा

Scenario: a complex form where changing the country dropdown updates the state list, toggles a tax field, and enables/disables Submit. If every widget talks directly to every other widget, you get an N×N tangle. A Mediator puts one coordinator in the middle: widgets report to it, and it orchestrates who updates whom.

परिस्थिती: एक जटिल form जिथे country dropdown बदलल्याने state-list update होते, tax field toggle होतं, आणि Submit enable/disable होतं. प्रत्येक widget प्रत्येक इतर widget शी थेट बोलला, तर N×N गुंता होतो. एक Mediator मध्ये एक coordinator ठेवतो: widgets त्याला कळवतात, आणि तो कोण कोणाला update करावं ते orchestrate करतो.

What
काय

Mediator: define an object that encapsulates how a set of objects interact, so they refer to the mediator instead of each other — turning many-to-many coupling into a hub-and-spoke.

Mediator: objects च्या संचाचा परस्परसंवाद encapsulate करणारा object ठरवा, म्हणून ते एकमेकांऐवजी mediator ला संदर्भतात — अनेक-ते-अनेक coupling ला hub-and-spoke मध्ये बदलून.

Why
का

It untangles N objects each knowing about the others (N² links) into N objects knowing one mediator. Interaction logic lives in one place, not smeared across components.

ते N objects प्रत्येकाला इतरांबद्दल माहीत असणं (N² links) N objects एका mediator ला जाणणं मध्ये सोडवतं. Interaction logic एका ठिकाणी राहतं, components ओलांडून पसरत नाही.

How
कसं

Components notify the mediator of events; the mediator holds the coordination rules and tells the relevant components what to do. Components don't reference each other.

Components mediator ला events कळवतात; mediator coordination नियम धरतो आणि संबंधित components ना काय करायचं ते सांगतो. Components एकमेकांना संदर्भत नाहीत.

Hub-and-spoke instead of an N×N mesh
N×N mesh ऐवजी hub-and-spoke
  ❌ every widget ↔ every widget:        ✅ mediator hub:
     Country ── State                     Country ─┐
        │  ╲  ╱  │                        State  ──┤
     Tax ─── Submit  (tangled mesh)       Tax    ──┼─► Mediator (coordination rules)
                                          Submit ──┘
   N² connections → N connections to one coordinator; rules centralized
🧠
Air traffic control is the classic mediator: planes don't negotiate directly with each other — they all talk to the tower, which coordinates. A chat room mediates between participants; nobody wires peer-to-peer.
🧠
Air traffic control हे classic mediator: planes एकमेकांशी थेट negotiate करत नाहीत — सगळे tower शी बोलतात, जो समन्वय करतो. एक chat room participants मध्ये mediate करते; कुणी peer-to-peer wire करत नाही.
🎛️
A Redux/Vuex store acts as a mediator for UI components: components dispatch to the store and read from it, rather than talking to each other. The store centralizes the interaction logic and state.
🎛️
Redux/Vuex store mediator म्हणून वागतो UI components साठी: components store ला dispatch करतात आणि त्यातून वाचतात, एकमेकांशी न बोलता. Store interaction logic आणि state केंद्रित करतो.
A form mediator coordinating widgets
class FormMediator {
  constructor(private country: Select, private state: Select,
              private tax: Field, private submit: Button) {}
  notify(sender: string, event: string) {                 // components report here
    if (sender === 'country' && event === 'change') {
      this.state.loadFor(this.country.value)              // mediator decides the ripple
      this.tax.setVisible(this.country.value === 'US')
      this.submit.setEnabled(this.country.value !== '')
    }
  }
}
// widgets call mediator.notify('country','change') — they never touch each other
🧒
In plain wordsWhen lots of parts all need to talk to lots of other parts, it becomes a spaghetti mess. So you put one coordinator in the middle — everyone talks only to it, and it tells the right parts what to do. Like air traffic control: planes don't call each other, they all talk to the tower, and the tower keeps everyone organized.
🧒
सोप्या शब्दांतजेव्हा भरपूर भागांना भरपूर इतर भागांशी बोलायचं असतं, तेव्हा तो spaghetti गोंधळ होतो. म्हणून मध्ये एक coordinator ठेवा — प्रत्येक फक्त त्याच्याशी बोलतो, आणि तो योग्य भागांना काय करायचं ते सांगतो. air traffic control सारखं: planes एकमेकांना call करत नाहीत, सगळे tower शी बोलतात, आणि tower सगळ्यांना व्यवस्थित ठेवतो.
🌍
In the wildRedux/Vuex/NgRx stores mediating components, air-traffic-control-style coordinators, chat servers, UI dialog controllers coordinating widgets, MVC/MVP controllers/presenters, and (at service scale) message brokers / API gateways coordinating microservices. Note: an in-app event bus blurs into Pub/Sub (topic 26) — Mediator is about centralized coordination logic, not just message passing.
🌍
प्रत्यक्ष वापरातRedux/Vuex/NgRx stores components mediate करणारे, air-traffic-control-शैली coordinators, chat servers, widgets समन्वयित करणारे UI dialog controllers, MVC/MVP controllers/presenters, आणि (service-scale ला) microservices समन्वयित करणारे message brokers / API gateways. टीप: in-app event bus Pub/Sub (topic 26) मध्ये मिसळतो — Mediator म्हणजे केंद्रित coordination logic.

✅ Do

  • Use it when many components interact in complex ways
  • Centralize the interaction rules in the mediator
  • Keep components referencing only the mediator, not each other

✅ हे करा

  • अनेक components जटिल पद्धतीने संवाद करतात तेव्हा वापरा
  • interaction नियम mediator मध्ये केंद्रित करा
  • components फक्त mediator ला संदर्भू द्या, एकमेकांना नाही

❌ Don't

  • Let the mediator absorb all logic and become a God object (topic 28)
  • Use it for simple one-to-one interactions that don't need coordination

❌ हे टाळा

  • mediator ला सगळं logic शोषून God object होऊ देऊ नका (topic 28)
  • समन्वय न लागणाऱ्या साध्या one-to-one interactions साठी वापरू नका
Gotcha: the mediator's whole job is to hold coordination complexity — which means it tends to grow into a God object (topic 28). You untangled the components, but if you're not careful you concentrated all that tangle into one 1,500-line coordinator that every feature must edit. Keep it focused on coordination, push real domain logic into the components/services, and split the mediator when it spans unrelated concerns. Also distinguish it from Observer/Pub-Sub: Mediator centralizes who-tells-whom-what logic; a bare event bus just delivers messages without the coordination rules.
अडचण: mediator चं संपूर्ण काम coordination गुंतागुंत धरणं — म्हणजे तो God object होण्याकडे झुकतो (topic 28). तुम्ही components untangle केले, पण काळजी न घेतल्यास तुम्ही ती सगळी गुंत एका 1,500-ओळींच्या coordinator मध्ये केंद्रित केली जिला प्रत्येक feature edit करावं लागतं. तो coordination वर केंद्रित ठेवा, खरं domain logic components/services मध्ये ढकला, आणि तो असंबंधित concerns ओलांडत असेल तर तोडा.
23Memento — capture & restore state (undo)Memento — state पकडा आणि पुनर्संचयित करा (undo)

Scenario: a document editor needs undo. You must snapshot the document's state before each change and restore it later — without exposing or breaking the document's encapsulation. The Memento pattern captures an object's internal state into an opaque token you can stash and later use to roll back.

परिस्थिती: एका document editor ला undo लागतं. तुम्ही प्रत्येक बदलाआधी document चं state snapshot घ्यावं आणि नंतर पुनर्संचयित करावं — document ची encapsulation न उघडता किंवा न मोडता. Memento pattern object चं internal state एका opaque token मध्ये पकडतं जो तुम्ही साठवू आणि नंतर roll back करायला वापरू शकता.

What
काय

Memento: capture an object's internal state in a token (the memento) that can be stored externally and used to restore the object later, without violating its encapsulation.

Memento: object चं internal state एका token (memento) मध्ये पकडा जो बाहेर साठवता येतो आणि नंतर object पुनर्संचयित करायला वापरता येतो, त्याची encapsulation न मोडता.

Why
का

It's how you implement undo/redo, checkpoints, and rollbacks: save known-good snapshots and return to them, while the object keeps its internals private.

undo/redo, checkpoints, आणि rollbacks असे राबवता: known-good snapshots साठवा आणि त्यांकडे परत जा, तर object त्याचे internals private ठेवतो.

How
कसं

The object creates a memento of its state and can restore from one; a "caretaker" (e.g. an undo stack) holds mementos but treats them as opaque.

object त्याच्या state चं memento बनवतो आणि एका मधून पुनर्संचयित करू शकतो; एक 'caretaker' (उदा. undo stack) mementos धरतो पण त्यांना opaque मानतो.

Snapshot now, roll back later
आता snapshot, नंतर roll back
   Editor.save()   ──► Memento{ text, cursor }  ──► pushed onto undo-stack (caretaker)
   ...user edits...
   Editor.undo()   ◄── pop Memento  ◄── restore(state)
   caretaker holds snapshots but can't read/alter them — encapsulation preserved
🧠
Game save states and DB savepoints are mementos: capture everything needed to resume/rollback to a point. A VM/container snapshot or a filesystem snapshot (ZFS, Time Machine) is the same idea at a different scale.
🧠
Game save states आणि DB savepoints म्हणजे mementos: एका बिंदूला resume/rollback करायला लागणारं सगळं पकडा. VM/container snapshot किंवा filesystem snapshot (ZFS, Time Machine) तीच कल्पना, वेगळ्या scale ला.
↩️
Undo/redo stacks pair Memento with Command (topic 17): commands do the work; mementos remember the before-state so undo() can restore it. React's state snapshots and form "reset to saved" work this way.
↩️
Undo/redo stacks Memento ला Command (topic 17) सोबत जोडतात: commands काम करतात; mementos before-state लक्षात ठेवतात म्हणून undo() पुनर्संचयित करू शकतो. React चे state snapshots आणि form 'reset to saved' असंच चालतं.
Editor snapshots + an undo caretaker
class EditorMemento { constructor(readonly text: string, readonly cursor: number) {} }

class Editor {
  private text = ''; private cursor = 0
  type(s: string){ this.text += s; this.cursor += s.length }
  save(): EditorMemento { return new EditorMemento(this.text, this.cursor) }  // snapshot
  restore(m: EditorMemento){ this.text = m.text; this.cursor = m.cursor }     // roll back
}
class History {                                   // caretaker — holds opaque mementos
  private stack: EditorMemento[] = []
  backup(e: Editor){ this.stack.push(e.save()) }
  undo(e: Editor){ const m = this.stack.pop(); if (m) e.restore(m) }
}
🧒
In plain wordsBefore you change something, take a snapshot — like a save point in a video game. Keep the snapshots in a pile. If you mess up, grab the last snapshot and go back to exactly how things were. The nice part: the snapshot is a sealed package — the pile that stores it doesn't get to peek inside or mess with the object's private stuff.
🧒
सोप्या शब्दांतकाही बदलण्याआधी, एक snapshot घ्या — video game मधल्या save point सारखं. Snapshots एका ढिगात ठेवा. गडबड झाली तर शेवटचा snapshot घ्या आणि गोष्टी नेमक्या होत्या तशा परत जा. छान भाग: snapshot म्हणजे बंद packet — ते साठवणारा ढीग आत डोकावत नाही किंवा object च्या private गोष्टींशी छेडछाड करत नाही.
🌍
In the wildUndo/redo in editors (VS Code, Word, Figma), game save states, database savepoints and transaction rollback, VM/container/filesystem snapshots (ZFS, Time Machine), React/form "reset to saved," event-sourcing snapshots, and browser history state. Often paired with Command (topic 17) to build a full undo system.
🌍
प्रत्यक्ष वापरातeditors मधले undo/redo (VS Code, Word, Figma), game save states, database savepoints आणि transaction rollback, VM/container/filesystem snapshots (ZFS, Time Machine), React/form 'reset to saved', event-sourcing snapshots, आणि browser history state. पूर्ण undo system बांधायला अनेकदा Command (topic 17) सोबत जोडलं जातं.

✅ Do

  • Use it for undo/redo, checkpoints, and rollback
  • Keep the memento opaque to the caretaker (only the originator reads it)
  • Snapshot only the state you actually need to restore

✅ हे करा

  • undo/redo, checkpoints, आणि rollback साठी वापरा
  • memento caretaker ला opaque ठेवा (फक्त originator ते वाचतो)
  • फक्त पुनर्संचयित करायला लागणारं state snapshot करा

❌ Don't

  • Snapshot huge state on every keystroke without limits (memory blowup)
  • Let external code depend on the memento's internal shape

❌ हे टाळा

  • प्रत्येक keystroke वर मोठं state मर्यादेशिवाय snapshot करू नका (memory स्फोट)
  • external code ला memento च्या internal आकारावर अवलंबून राहू देऊ नका
Gotcha: naïve Memento is a memory hog — full snapshots of large state on every change can balloon fast. Real systems bound it: cap the undo stack, snapshot at intervals, or store diffs instead of full copies (which shades into the Command pattern's "store the reverse operation"). There's also a deep-copy trap identical to Prototype's (topic 6): if the memento holds references into the live object's mutable internals instead of a copy, later edits corrupt your "saved" snapshot. Capture an immutable, deep copy of exactly the state you need to restore — no more, no less.
अडचण: भोळं Memento memory खाणारं — प्रत्येक बदलावर मोठ्या state चे पूर्ण snapshots झपाट्याने फुगू शकतात. खऱ्या systems ते मर्यादित करतात: undo stack cap करा, अंतराने snapshot करा, किंवा पूर्ण प्रती ऐवजी diffs साठवा. Prototype (topic 6) सारखा deep-copy सापळा ही: memento जिवंत object च्या mutable internals मध्ये references धरत असेल copy ऐवजी, तर नंतरचे edits तुमचा 'saved' snapshot दूषित करतात. immutable, deep प्रत पकडा.
24Visitor — add operations to a type hierarchyVisitor — type hierarchy वर operations जोडा

Scenario: you have an AST (abstract syntax tree) of node types — NumberLit, BinaryOp, FunctionCall — and you keep needing new operations over it: evaluate, pretty-print, type-check, minify. Adding each as a method on every node type bloats the nodes. Visitor lets you add new operations without touching the node classes — each operation is a separate visitor.

परिस्थिती: तुमच्याकडे node types चं AST आहे — NumberLit, BinaryOp, FunctionCall — आणि तुम्हाला त्यावर सतत नवीन operations लागतात: evaluate, pretty-print, type-check, minify. प्रत्येक node type वर method म्हणून जोडणं nodes फुगवतं. Visitor तुम्हाला node classes ला हात न लावता नवीन operations जोडू देतं — प्रत्येक operation एक वेगळा visitor.

What
काय

Visitor: represent an operation to perform on the elements of an object structure as a separate visitor object, so you can add new operations without changing the element classes.

Visitor: object रचनेच्या elements वर करायचं operation एका वेगळ्या visitor object म्हणून दर्शवा, म्हणून तुम्ही element classes न बदलता नवीन operations जोडू शकता.

Why
का

When the set of types is stable but you add operations often (compilers, linters), Visitor keeps each operation in one cohesive place instead of scattering it across every type.

types चा संच स्थिर पण तुम्ही operations अनेकदा जोडता (compilers, linters) तेव्हा Visitor प्रत्येक operation प्रत्येक type मध्ये विखुरण्याऐवजी एका सुसंगत ठिकाणी ठेवतो.

How
कसं

Each element has accept(visitor) that calls the visitor's method for its type (double dispatch). Each operation is one visitor implementing a method per element type.

प्रत्येक element ला accept(visitor) असतो जो त्याच्या type साठी visitor ची method call करतो (double dispatch). प्रत्येक operation म्हणजे प्रति element type एक method राबवणारा एक visitor.

Operations live in visitors, not in the nodes
Operations visitors मध्ये राहतात, nodes मध्ये नाही
   AST nodes (stable):   NumberLit   BinaryOp   FunctionCall
        each has accept(v) ─► calls v.visitNumberLit(this) etc. (double dispatch)
   operations (grow often), each a visitor:
        EvalVisitor      visitNumberLit  visitBinaryOp  visitFunctionCall
        PrintVisitor     "                                             "
        TypeCheckVisitor "                                             "
   add an operation → new visitor class. node classes never change.
🧠
Compilers and linters run on Visitor: Babel, ESLint, TypeScript, and Roslyn traverse the AST with visitors — one visitor per pass (lint rule, transform, type-check). The node types are fixed; the passes multiply.
🧠
Compilers आणि linters Visitor वर चालतात: Babel, ESLint, TypeScript, आणि Roslyn AST visitors ने traverse करतात — प्रति pass एक visitor (lint rule, transform, type-check). Node types निश्चित; passes वाढतात.
🩺
A tax auditor visiting different account types: the accounts (checking, savings, brokerage) are stable; the auditor (visitor) knows how to process each. Next year a new audit (visitor) arrives without changing the accounts.
🩺
वेगवेगळ्या account types ला भेट देणारा tax auditor: accounts (checking, savings, brokerage) स्थिर; auditor (visitor) प्रत्येक कसं process करायचं जाणतो. पुढच्या वर्षी नवीन audit (visitor) येतो, accounts न बदलता.
Visitors add operations without touching node classes
interface Visitor { number(n: NumberLit): any; binary(b: BinaryOp): any }
interface Node { accept(v: Visitor): any }

class NumberLit implements Node { constructor(readonly value: number){}
  accept(v: Visitor){ return v.number(this) } }             // double dispatch
class BinaryOp implements Node { constructor(readonly op: string, readonly l: Node, readonly r: Node){}
  accept(v: Visitor){ return v.binary(this) } }

class EvalVisitor implements Visitor {                       // one operation = one visitor
  number(n){ return n.value }
  binary(b){ const l=b.l.accept(this), r=b.r.accept(this); return b.op==='+'? l+r : l*r }
}
// add PrintVisitor / TypeCheckVisitor later — NumberLit/BinaryOp never change
🧒
In plain wordsImagine a fixed set of shapes, and you keep inventing new things to do to them (measure them, paint them, count them). Instead of teaching every shape every new trick, you make a "visitor" for each trick that knows how to handle each shape. Want a new trick? Write a new visitor. The shapes never change — which is perfect when the shapes are settled but the list of things-to-do keeps growing.
🧒
सोप्या शब्दांतएका निश्चित shapes च्या संचाची कल्पना करा, आणि तुम्ही त्यांना करायच्या नवीन गोष्टी सतत शोधता (मोजा, रंगवा, count करा). प्रत्येक shape ला प्रत्येक नवीन युक्ती शिकवण्याऐवजी, तुम्ही प्रत्येक युक्तीसाठी एक 'visitor' बनवता जो प्रत्येक shape कसं हाताळायचं जाणतो. नवीन युक्ती हवी? नवीन visitor लिहा. Shapes कधीच बदलत नाहीत — जे shapes स्थिर पण करायच्या गोष्टींची यादी वाढत असताना योग्य.
🌍
In the wildCompilers & linters (Babel, ESLint, TypeScript, Roslyn, LLVM passes) traversing ASTs, document/DOM tree processing, serialization visitors, query-plan walkers in databases, and IR optimization passes. It's the go-to when you have a fixed node hierarchy and a growing set of operations over it.
🌍
प्रत्यक्ष वापरातCompilers आणि linters (Babel, ESLint, TypeScript, Roslyn, LLVM passes) ASTs traverse करणारे, document/DOM tree processing, serialization visitors, databases मधले query-plan walkers, आणि IR optimization passes. निश्चित node hierarchy आणि त्यावर वाढणाऱ्या operations असतील तेव्हा हे go-to.

✅ Do

  • Use it when the type set is stable but operations grow (ASTs, document trees)
  • Keep each operation cohesive in one visitor
  • Use it to run many passes over the same structure

✅ हे करा

  • type संच स्थिर पण operations वाढतात तेव्हा वापरा (ASTs, document trees)
  • प्रत्येक operation एका visitor मध्ये सुसंगत ठेवा
  • एकाच रचनेवर अनेक passes चालवायला वापरा

❌ Don't

  • Use it when you frequently add new element types (every visitor must then change)
  • Reach for it in languages with pattern matching / sum types — a match is simpler

❌ हे टाळा

  • तुम्ही वारंवार नवीन element types जोडता तेव्हा वापरू नका (मग प्रत्येक visitor बदलावा लागतो)
  • pattern matching / sum types असलेल्या languages मध्ये घेऊ नका — match सोपं
Gotcha: Visitor makes a hard tradeoff: adding an operation is cheap (one new visitor), but adding an element type is expensive (you must add a method to every visitor). So it's right only when the type hierarchy is stable and operations churn — the exact opposite situation wants operations-as-methods-on-types instead. This is the "expression problem." Also, Visitor is the most boilerplate-heavy GoF pattern (double dispatch, a method per type); in languages with pattern matching / algebraic data types (Rust, Scala, Kotlin, TS discriminated unions), a switch/match on node type is simpler and does the same job — reserve classic Visitor for OO languages lacking that.
अडचण: Visitor एक कठीण तडजोड करतो: operation जोडणं स्वस्त (एक नवीन visitor), पण element type जोडणं महाग (तुम्हाला प्रत्येक visitor मध्ये method जोडावी लागते). म्हणून type hierarchy स्थिर आणि operations बदलत असतील तेव्हाच योग्य — नेमकी उलट परिस्थिती operations-as-methods-on-types हवी. हा 'expression problem.' आणि Visitor सर्वात boilerplate-जड GoF pattern; pattern matching असलेल्या languages मध्ये (Rust, Scala, Kotlin, TS discriminated unions) node type वर match सोपं आणि तेच काम करतं.
Part IV · Modern & Architectural
भाग IV · Modern आणि Architectural
25Repository — a collection-like API over storageRepository — storage वर collection-सारखं API★ backbone★ कणा

Scenario: your business logic needs to load and save Orders, but you don't want SQL (or Mongo queries, or HTTP calls) smeared through it. A Repository gives your domain a collection-like interface — orders.byId(id), orders.save(order) — and hides whether that's Postgres, an API, or an in-memory list (crucial for tests).

परिस्थिती: तुमच्या business logic ला Orders load आणि save करायचे आहेत, पण तुम्हाला SQL (किंवा Mongo queries, किंवा HTTP calls) त्यात विखुरलेले नकोत. एक Repository तुमच्या domain ला collection-सारखं interface देतं — orders.byId(id), orders.save(order) — आणि ते Postgres आहे, API आहे, की in-memory list आहे हे लपवतं (tests साठी महत्त्वाचं).

What
काय

Repository: mediate between the domain and data-mapping layers with a collection-like interface for accessing domain objects, hiding the persistence mechanism.

Repository: domain आणि data-mapping layers मध्ये domain objects access करायला collection-सारखं interface देतं, persistence यंत्रणा लपवून.

Why
का

It keeps persistence details out of business logic, centralizes queries, and lets you swap the real database for an in-memory fake in tests (Dependency Inversion, OOP topic 17).

ते persistence तपशील business logic बाहेर ठेवतं, queries केंद्रित करतं, आणि tests मध्ये खरा database in-memory fake ने बदलू देतं (Dependency Inversion, OOP topic 17).

How
कसं

Define a repository interface in the domain (save, byId, findBy…); implement it per datastore in the infrastructure layer; inject it into services.

domain मध्ये repository interface ठरवा (save, byId, findBy…); प्रति datastore infrastructure layer मध्ये राबवा; services मध्ये inject करा.

Domain talks to a collection, not to SQL
Domain collection शी बोलतो, SQL शी नाही
   OrderService ──► «OrderRepository { byId  save  findOpenFor(customer) }»
                             ▲                         ▲
                    PostgresOrderRepo          InMemoryOrderRepo (tests)
   business logic never writes SQL; swap the implementation without touching it
🧠
It combines Facade + Adapter + DIP: a simple collection facade over your data store, adapting the DB's API to your domain's language, with the interface owned by the domain so you can inject a fake. Spring Data and Rails/Django's ORM layers institutionalize it.
🧠
ते Facade + Adapter + DIP एकत्र करतं: तुमच्या data store वर एक साधं collection facade, DB चं API तुमच्या domain च्या भाषेत adapting, आणि interface domain च्या मालकीचं म्हणून तुम्ही fake inject करू शकता. Spring Data आणि Rails/Django चे ORM layers ते संस्थात्मक करतात.
🗂️
Like an in-memory list you can query: orders.findOpenFor(customer) reads like operating on a collection — you don't think about tables, joins, or connection pools; the repository handles the mapping.
🗂️
query करता येणाऱ्या in-memory list सारखं: orders.findOpenFor(customer) collection वर operate केल्यासारखं वाचतं — तुम्ही tables, joins, किंवा connection pools विचारत नाही; repository mapping सांभाळतं.
Domain-owned interface, swappable implementations
interface OrderRepository {                         // defined in the DOMAIN
  byId(id: string): Promise<Order | null>
  save(order: Order): Promise<void>
  findOpenFor(customerId: string): Promise<Order[]>
}
class PostgresOrderRepo implements OrderRepository { /* SQL + row↔Order mapping */ }
class InMemoryOrderRepo implements OrderRepository {   // for fast, DB-free tests
  private store = new Map<string, Order>()
  async byId(id){ return this.store.get(id) ?? null }
  async save(o){ this.store.set(o.id, o) }
  async findOpenFor(c){ return [...this.store.values()].filter(o => o.customerId===c && o.isOpen()) }
}
// OrderService(new PostgresOrderRepo()) in prod;  OrderService(new InMemoryOrderRepo()) in tests
🧒
In plain wordsYour main code shouldn't be full of database commands. A repository is like a librarian: you say "get me order #42" or "save this order," and the librarian deals with the shelves (the database) for you. Your code just asks in plain terms. And for testing, you can swap the real library for a tiny pretend one — same requests, no real database needed.
🧒
सोप्या शब्दांततुमचं मुख्य code database commands नी भरलेलं नसावं. Repository म्हणजे librarian सारखं: तुम्ही 'order #42 दे' किंवा 'ही order save कर' म्हणता, आणि librarian तुमच्यासाठी शेल्फ (database) हाताळतो. तुमचा code फक्त साध्या शब्दांत विचारतो. आणि testing साठी तुम्ही खरी library एका छोट्या pretend ने बदलू शकता — तेच requests, खरा database नको.
🌍
In the wildSpring Data repositories, Rails/Django ORM layers, .NET EF Core + repository, DDD aggregates loaded/saved via repositories, and the standard "swap Postgres for an in-memory repo in unit tests" pattern. It underpins hexagonal/clean architecture (the repository is a "port"). See the N+1 and DR case studies for how repository queries meet real database behavior.
🌍
प्रत्यक्ष वापरातSpring Data repositories, Rails/Django ORM layers, .NET EF Core + repository, repositories मधून load/save होणारे DDD aggregates, आणि standard 'unit tests मध्ये Postgres in-memory repo ने बदला' pattern. हे hexagonal/clean architecture ला आधार देतं (repository म्हणजे 'port'). Repository queries खऱ्या database behavior ला कशा भेटतात हे N+1 आणि DR case studies पाहा.

✅ Do

  • Define the repository interface in the domain; implement per datastore
  • Keep query logic in the repository, not scattered in services
  • Provide an in-memory implementation for fast tests

✅ हे करा

  • repository interface domain मध्ये ठरवा; प्रति store राबवा
  • query logic repository मध्ये ठेवा, services मध्ये विखुरून नाही
  • जलद tests साठी in-memory implementation द्या

❌ Don't

  • Leak the ORM's query builder / SQL types through the interface
  • Add a repository over a trivial CRUD app where the ORM already suffices

❌ हे टाळा

  • interface मधून ORM चा query builder / SQL types गळू देऊ नका
  • ORM आधीच पुरेसा असलेल्या क्षुल्लक CRUD app वर repository जोडू नका
Gotcha: two real-world traps. First, a leaky repository that returns the ORM's query builder or exposes IQueryable/lazy relations pushes persistence concerns right back into your domain — and reintroduces the N+1 problem when callers trigger lazy loads (Proxy, topic 11). Keep the interface in your terms, returning fully-loaded domain objects. Second, over a simple CRUD app the ORM's own models are often already a repository-ish abstraction — wrapping them in another layer is ceremony (see anti-patterns, topic 28). Add the pattern when you have real domain logic to protect from persistence detail, not reflexively.
अडचण: दोन खरे सापळे. पहिला, leaky repository जो ORM चा query builder परत करतो किंवा IQueryable/lazy relations उघड करतो, तो persistence concerns तुमच्या domain मध्ये परत ढकलतो — आणि callers lazy loads trigger करताच N+1 problem पुन्हा आणतो (Proxy, topic 11). interface तुमच्या भाषेत ठेवा, पूर्ण-loaded domain objects परत करत. दुसरा, साध्या CRUD app वर ORM चे स्वतःचे models आधीच repository-ish abstraction — त्यांना आणखी एका layer मध्ये गुंडाळणं सोपस्कार (topic 28).
26Pub/Sub & the event bus — decouple across servicesPub/Sub आणि event bus — services ओलांडून decouple करा

Scenario: when an order is placed, billing, shipping, analytics, and email must all react — and you want to add reactions later without redeploying the order service. Publish/Subscribe broadcasts an OrderPlaced event to a broker; any number of independent services subscribe. It's Observer (topic 16) scaled across processes and machines.

परिस्थिती: order place होताच billing, shipping, analytics, आणि email सगळ्यांनी प्रतिक्रिया द्यावी — आणि तुम्हाला order service redeploy न करता नंतर प्रतिक्रिया जोडायच्या आहेत. Publish/Subscribe एक OrderPlaced event एका broker ला broadcast करतं; कितीही स्वतंत्र services subscribe करतात. हे Observer (topic 16) processes आणि machines ओलांडून scaled.

What
काय

Pub/Sub: publishers emit events to a broker/topic without knowing who consumes them; subscribers register interest and receive events independently. An event bus is the in-app version.

Pub/Sub: publishers events एका broker/topic ला emit करतात कोण consume करतं हे न जाणता; subscribers रस register करतात आणि events स्वतंत्रपणे मिळवतात. event bus ही in-app आवृत्ती.

Why
का

It fully decouples producers from consumers (in time and space): add/remove/scale consumers without touching producers, absorb load spikes via the broker, and enable event-driven architectures.

ते producers ना consumers पासून पूर्ण decouple करतं (वेळ आणि जागेत): producers ला हात न लावता consumers जोडा/काढा/scale करा, broker द्वारे load spikes शोषा, आणि event-driven architectures शक्य करा.

How
कसं

Publish events to a topic on a broker (Kafka, RabbitMQ, Redis, SNS/SQS); consumers subscribe and process asynchronously, each at its own pace, with the broker handling delivery/retries.

events एका broker वरच्या topic ला publish करा (Kafka, RabbitMQ, Redis, SNS/SQS); consumers subscribe करतात आणि asynchronously process करतात, प्रत्येक स्वतःच्या वेगाने, broker delivery/retries हाताळतो.

Broadcast to a broker; consumers are independent
Broker ला broadcast; consumers स्वतंत्र
   OrderService ──publish(OrderPlaced)──► [ topic on broker ]
                                              ├─► Billing consumer
                                              ├─► Shipping consumer
                                              ├─► Analytics consumer
                                              └─► Email consumer
   producer doesn't know consumers exist. add one → subscribe, no producer change.
🧠
Kafka / RabbitMQ / SNS+SQS are pub/sub at scale: the order service just emits an event; a dozen teams' services consume it independently, replay it, and scale separately. It's the foundation of event-driven microservices (see the Event-Driven playbook).
🧠
Kafka / RabbitMQ / SNS+SQS म्हणजे scale ला pub/sub: order service फक्त event emit करतं; डझनभर teams च्या services ते स्वतंत्रपणे consume करतात, replay करतात, आणि वेगळं scale करतात. Event-driven microservices चा पाया (Event-Driven playbook पाहा).
📻
A radio broadcast: the station transmits; any number of radios tune in, and the station neither knows nor cares who's listening. New listeners need no change at the station — they just tune to the frequency (topic).
📻
Radio broadcast: station प्रसारित करतं; कितीही radios tune करतात, आणि station ला कोण ऐकतंय हे कळत नाही की पर्वा नाही. नवीन listeners ना station ला बदल नको — ते फक्त frequency (topic) वर tune करतात.
Publish an event; independent consumers react
// producer — knows only the broker + topic, not the consumers
await broker.publish('order.placed', { orderId: order.id, total: order.total().cents })

// each consumer subscribes independently, processes asynchronously, at its own pace
broker.subscribe('order.placed', async (evt) => { await billing.invoice(evt.orderId) })
broker.subscribe('order.placed', async (evt) => { await shipping.schedule(evt.orderId) })
broker.subscribe('order.placed', async (evt) => { analytics.track('order', evt) })
// add a fraud-check consumer later → just subscribe. producer never changes.
🧒
In plain wordsWhen something important happens, instead of the order service personally phoning every other service, it just announces it once to a central board ("an order was placed!"). Any service that cares is watching the board and handles its own part — billing, shipping, email. You can add a new service that watches the board anytime, and the order service never has to know or change. It's the "shout it out" idea from Observer, but across many separate programs.
🧒
सोप्या शब्दांतकाही महत्त्वाचं घडतं (order place होते) तेव्हा order service प्रत्येक इतर service ला वैयक्तिकपणे फोन करण्याऐवजी, ते फक्त एका central board ला एकदा घोषित करतं ('order place झाली!'). ज्या service ला पर्वा आहे ती board पाहत असते आणि स्वतःचा भाग हाताळते — billing, shipping, email. तुम्ही कधीही board पाहणारी नवीन service जोडू शकता, आणि order service ला ते कळायचं किंवा बदलायचं नसतं.
🌍
In the wildKafka, RabbitMQ, Redis Pub/Sub, AWS SNS+SQS, Google Pub/Sub, NATS, and cloud event buses (EventBridge) — the backbone of event-driven microservices, domain events, CQRS/event sourcing, and the outbox pattern. In-process event buses (Node EventEmitter, Spring events, MediatR notifications) are the same shape without the network. Covered in depth in the Event-Driven Architecture playbook.
🌍
प्रत्यक्ष वापरातKafka, RabbitMQ, Redis Pub/Sub, AWS SNS+SQS, Google Pub/Sub, NATS, आणि cloud event buses (EventBridge) — event-driven microservices, domain events, CQRS/event sourcing, आणि outbox pattern चा कणा. In-process event buses (Node EventEmitter, Spring events, MediatR notifications) तोच आकार network शिवाय. Event-Driven Architecture playbook मध्ये सविस्तर.

✅ Do

  • Use it to decouple producers from many independent, scalable consumers
  • Make consumers idempotent (brokers deliver at-least-once → duplicates happen)
  • Plan for dead-letter queues and retries for poison messages

✅ हे करा

  • producers ना अनेक स्वतंत्र, scalable consumers पासून decouple करायला वापरा
  • consumers idempotent करा (brokers at-least-once देतात → duplicates होतात)
  • poison messages साठी dead-letter queues आणि retries योजा

❌ Don't

  • Assume exactly-once delivery or global ordering (usually you get neither for free)
  • Use async events where you need a synchronous, immediately-consistent answer

❌ हे टाळा

  • exactly-once delivery किंवा global ordering गृहीत धरू नका (सहसा फुकट मिळत नाही)
  • तात्काळ, consistent result लागतो तिथे async events वापरू नका
Gotcha: Pub/Sub trades tight coupling for distributed-systems hard problems. Most brokers give at-least-once delivery, so the same event can arrive twice — consumers must be idempotent or you'll double-charge a card. Ordering is often only per-partition, not global. Failures need dead-letter queues and retry policies (topic 27), and debugging becomes tracing an event across services rather than reading one stack. And it's eventually consistent: don't use it when the caller needs an immediate, consistent result. Distinguish it from in-process Observer (topic 16) — the moment a broker and the network are involved, these concerns are yours to handle. (See the Event-Driven and Distributed Systems playbooks.)
अडचण: Pub/Sub घट्ट coupling ला distributed-systems कठीण समस्यांशी बदलतं. बहुतेक brokers at-least-once delivery देतात, म्हणून तोच event दोनदा येऊ शकतो — consumers idempotent असलेच पाहिजेत नाहीतर तुम्ही कार्ड दुहेरी-charge कराल. Ordering अनेकदा फक्त per-partition, global नाही. अपयशांना dead-letter queues आणि retry policies लागतात (topic 27). आणि ते eventually consistent: caller ला तात्काळ, consistent उत्तर लागतं तेव्हा वापरू नका.
27Circuit Breaker & Retry — resilience against failureCircuit Breaker आणि Retry — अपयशाविरुद्ध resilience★ production★ production

Scenario: a downstream service you call gets slow/unhealthy. Naïve retries pile on and make it worse; every request hangs on a timeout, threads exhaust, and the failure cascades until your whole app is down. A Circuit Breaker detects the failures and "trips" — failing fast for a while instead of hammering a dying dependency — while Retry with backoff handles transient blips.

परिस्थिती: तुम्ही call करत असलेली downstream service मंद/अस्वस्थ होते. भोळे retries अधिक ढीग लावतात आणि गोष्टी बिघडवतात; प्रत्येक request timeout वर hang होते, threads संपतात, आणि अपयश cascade होतं जोपर्यंत तुमचा संपूर्ण app down होत नाही. एक Circuit Breaker अपयश ओळखून 'trip' होतो — मरणाऱ्या dependency वर ठोकण्याऐवजी थोडा वेळ जलद-अपयशी होतो — तर backoff सह Retry तात्पुरत्या blips हाताळतो.

What
काय

Circuit Breaker: wrap a risky call so that after N failures it "opens" and fails fast (skipping the call) for a cooldown, then "half-opens" to test recovery. Retry: re-attempt transient failures, ideally with exponential backoff + jitter.

Circuit Breaker: एका जोखमीच्या call ला गुंडाळा म्हणजे N अपयशांनंतर तो 'open' होतो आणि cooldown पर्यंत जलद-अपयशी होतो (call वगळून), मग recovery तपासायला 'half-open' होतो. Retry: तात्पुरत्या अपयशांचा पुन्हा-प्रयत्न, आदर्शतः exponential backoff + jitter सह.

Why
का

They stop one slow/broken dependency from cascading into a total outage: fail fast to shed load, give the dependency room to recover, and smooth over momentary glitches.

ते एका मंद/तुटलेल्या dependency ला संपूर्ण outage मध्ये cascade होण्यापासून थांबवतात: load टाकायला जलद-अपयशी व्हा, dependency ला recover व्हायला जागा द्या, आणि क्षणिक glitches वर smooth करा.

How
कसं

Track failures; on threshold, open the circuit (return an error/fallback immediately); after a timeout, half-open and probe. Combine with bounded retries, timeouts, and bulkheads.

अपयश track करा; threshold वर circuit open करा (लगेच error/fallback परत करा); timeout नंतर half-open करून probe करा. bounded retries, timeouts, आणि bulkheads सोबत जोडा.

Closed → Open → Half-open state machine
Closed → Open → Half-open state machine
          failures ≥ threshold
   CLOSED ──────────────────► OPEN ── fail fast (return fallback), skip the call
     ▲  (calls flow through)    │ after cooldown
     │  success                 ▼
     └──────────────────── HALF-OPEN ── let ONE probe through:
                                          success → CLOSED, failure → OPEN
   retry (bounded, exp. backoff + jitter) handles TRANSIENT blips underneath
🧠
It's an electrical circuit breaker: when current surges, it trips to protect the house instead of letting wires melt. Netflix's Hystrix, resilience4j (Java), and Polly (.NET) popularized it for microservices — trip on a failing dependency to protect the caller.
🧠
तो electrical circuit breaker आहे: current surge झाला की तो trip होतो घराचं रक्षण करायला, wires वितळू न देता. Netflix चं Hystrix, resilience4j (Java), आणि Polly (.NET) नी microservices साठी ते लोकप्रिय केलं.
🔁
Retry with backoff + jitter is how clients survive transient errors (a brief network blip, a 503): wait 1s, 2s, 4s… with randomness so a thousand clients don't retry in lockstep and create a "thundering herd" that re-downs the service.
🔁
Backoff + jitter सह Retry हे clients तात्पुरत्या errors वर कसे टिकतात: 1s, 2s, 4s… थांबा, randomness सह म्हणजे हजार clients एकसाथ retry करून service पुन्हा-down करणारा 'thundering herd' तयार करत नाहीत.
A minimal circuit breaker wrapping a call
class CircuitBreaker {
  private failures = 0; private openUntil = 0
  constructor(private threshold = 5, private cooldownMs = 30000) {}
  async call<T>(fn: () => Promise<T>, fallback: () => T): Promise<T> {
    if (Date.now() < this.openUntil) return fallback()          // OPEN → fail fast
    try {
      const r = await fn(); this.failures = 0; return r          // success → reset
    } catch (e) {
      if (++this.failures >= this.threshold)
        this.openUntil = Date.now() + this.cooldownMs            // trip OPEN
      return fallback()
    }
  }
}
// often composed as a Decorator (topic 9): breaker(retry(timeout(httpClient)))
🧒
In plain wordsIf something you rely on keeps failing, don't keep desperately calling it — that just makes things worse and freezes you up too. A circuit breaker notices the trouble and stops calling for a bit, so you fail quickly (maybe with a backup answer) and give the sick service time to recover. Retries handle the little hiccups by trying again after a short, growing wait. Together they keep one broken piece from dragging the whole system down.
🧒
सोप्या शब्दांततुम्ही अवलंबून असलेली गोष्ट सतत अपयशी होत असेल, तर तिला हताशपणे call करत राहू नका — त्याने गोष्टी बिघडतात आणि तुम्हीही गोठता. Circuit breaker त्रास ओळखून थोडा वेळ call थांबवतो, म्हणून तुम्ही जलद अपयशी होता (कदाचित backup उत्तरासह) आणि आजारी service ला recover व्हायला वेळ देता. Retries लहान hiccups थोड्या, वाढत्या थांब्यानंतर पुन्हा प्रयत्न करून हाताळतात. एकत्र ते एका तुटलेल्या तुकड्याला संपूर्ण system खाली ओढू देत नाहीत.
🌍
In the wildNetflix Hystrix, resilience4j, Polly (.NET), Istio/Envoy circuit breaking, AWS SDK adaptive retries, gRPC retry policies, and service meshes. Paired with timeouts, bulkheads (isolate resource pools), and rate limiting as the standard resilience toolkit. Frequently implemented as stacked Decorators (topic 9) around a client. Core to the Distributed Systems playbook's resilience chapter.
🌍
प्रत्यक्ष वापरातNetflix Hystrix, resilience4j, Polly (.NET), Istio/Envoy circuit breaking, AWS SDK adaptive retries, gRPC retry policies, आणि service meshes. timeouts, bulkheads (resource pools वेगळे करा), आणि rate limiting सोबत standard resilience toolkit म्हणून जोडलं जातं. अनेकदा client भोवती रचलेले Decorators (topic 9) म्हणून राबवलं. Distributed Systems playbook च्या resilience प्रकरणाचा गाभा.

✅ Do

  • Add timeouts + bounded retries with exponential backoff and jitter
  • Trip a circuit breaker on a failing dependency and serve a fallback
  • Only retry idempotent/safe operations

✅ हे करा

  • timeouts + bounded retries exponential backoff आणि jitter सह जोडा
  • अपयशी dependency वर circuit breaker trip करा आणि fallback द्या
  • फक्त idempotent/safe operations retry करा

❌ Don't

  • Retry without backoff/jitter (you create a thundering-herd retry storm)
  • Retry non-idempotent calls (a "failed" charge may have succeeded → double charge)

❌ हे टाळा

  • backoff/jitter शिवाय retry करू नका (retry storm तयार करता)
  • non-idempotent calls retry करू नका ('अपयशी' charge यशस्वी झालेलं असू शकतं → दुहेरी charge)
Gotcha: retries are dangerous on non-idempotent operations — a payment that timed out may actually have succeeded, so a blind retry double-charges. Use idempotency keys, or only retry safe reads. And retries without exponential backoff + jitter cause retry storms: when a service wobbles, synchronized client retries pile on and finish it off (the "thundering herd"). Tune breaker thresholds carefully too — too sensitive and you trip on normal blips; too lax and you never protect the caller. Resilience patterns are essential in production but must be configured with these failure modes in mind, not just switched on. (See the Distributed Systems playbook.)
अडचण: retries non-idempotent operations वर धोकादायक — timeout झालेलं payment खरंतर यशस्वी झालेलं असू शकतं, म्हणून भोळा retry दुहेरी-charge करतो. idempotency keys वापरा, किंवा फक्त safe reads retry करा. आणि exponential backoff + jitter शिवाय retries retry storms घडवतात: service डगमगली की synchronized client retries ढीग लावून तिला संपवतात ('thundering herd'). Breaker thresholds ही काळजीपूर्वक tune करा. Resilience patterns production मध्ये आवश्यक पण या failure modes लक्षात घेऊन configure करा.
28Anti-patterns — patterns misused, and pattern feverAnti-patterns — गैरवापरलेले patterns, आणि pattern-ताप★ judgment★ सारासार विवेक

Scenario: a codebase where everything is a *Manager or *Factory, there are five layers of interfaces with one implementation each, a 3,000-line ApplicationService God object, and Singletons everywhere. Every pattern was applied — and the result is harder to change than plain code. Knowing the patterns includes knowing how they go wrong.

परिस्थिती: अशी codebase जिथे सगळं *Manager किंवा *Factory आहे, एकच-implementation असलेल्या पाच interface-स्तर आहेत, एक 3,000-ओळींची ApplicationService God object आहे, आणि Singletons सगळीकडे. प्रत्येक pattern लावलं — आणि निकाल साध्या code पेक्षा बदलायला कठीण. Patterns जाणण्यात ते कसे बिघडतात हे जाणणंही येतं.

What
काय

Anti-patterns are common "solutions" that look reasonable but backfire — including overusing the very patterns in this playbook. The senior skill is restraint and fit.

Anti-patterns म्हणजे वाजवी वाटणारे पण उलटे परिणाम देणारे सामान्य 'उपाय' — या playbook मधले patterns अति-वापरणं यासह. senior कौशल्य म्हणजे संयम आणि योग्यता.

Why
का

Patterns are tools with costs (indirection, ceremony, cognitive load). Applied where their problem doesn't exist, they add complexity without benefit — the opposite of their purpose.

Patterns खर्चासह साधनं (indirection, सोपस्कार, cognitive load). त्यांची समस्या नसताना लावले, तर ते फायद्याशिवाय गुंतागुंत जोडतात — त्यांच्या हेतूचं उलट.

How
कसं

Reach for a pattern only when you have evidence of its problem (repetition, real variation, a genuine boundary). Prefer the simplest thing that works; refactor toward a pattern when the need appears.

एक pattern तेव्हाच घ्या जेव्हा त्याच्या समस्येचा पुरावा असेल (पुनरावृत्ती, खरी variation, खरी सीमा). चालणारी सर्वात साधी गोष्ट ला झुकतं द्या; गरज दिसताच एका pattern कडे refactor करा.

The usual suspects
नेहमीचे संशयित
   God Object / Blob   one class does everything; every change touches it
   Singleton abuse     global mutable state disguised as a pattern (topic 5)
   Pattern fever /      forcing Factory/Visitor/Strategy where a function/if/enum
     "Golden Hammer"    would do — complexity for its own sake
   Premature abstraction  interfaces & layers for variation that never comes (YAGNI)
   Spaghetti (no structure)  ↔  Lasagna (too many thin indirection layers)
   Poltergeist          classes that just pass calls through, adding nothing
🧠
The "Golden Hammer": "when all you have is a hammer, everything looks like a nail." A dev who just learned Visitor visitors everything; a team that discovered microservices splits a CRUD app into 30 of them. The tool isn't wrong — the reflexive over-application is.
🧠
'Golden Hammer': 'जेव्हा तुमच्याकडे फक्त hammer असतो, सगळं खिळ्यासारखं दिसतं.' Visitor नुकतं शिकलेला dev सगळ्यावर visitors लावतो; microservices शोधलेली team CRUD app 30 मध्ये तोडते. साधन चूक नाही — reflexive अति-वापर आहे.
⚖️
Two ditches on one road: spaghetti (no patterns, everything tangled) and lasagna (so many abstraction layers you can't find the actual logic). Good design steers between under- and over-engineering — the same calibration point as OOP's "when NOT to use OOP."
⚖️
एका रस्त्यावर दोन खड्डे: spaghetti (patterns नाहीत, सगळं गुंतलेलं) आणि lasagna (इतके abstraction स्तर की खरं logic सापडत नाही). चांगला design under- आणि over-engineering मध्ये चालतो.
Over-patterned vs simple-and-correct
// ❌ pattern fever: a Strategy + Factory + Singleton to add two numbers
const adder = AdderStrategyFactory.getInstance().create('sum')
adder.execute(a, b)

// ✅ it's a function.
const sum = (a: number, b: number) => a + b

// ❌ premature abstraction: 1 interface, 1 impl, no test double, no 2nd impl in sight
interface IUserNameFormatter { format(u: User): string }
class UserNameFormatterImpl implements IUserNameFormatter { /* ... */ }
// ✅ just a function until a real second case or a test seam actually appears
const formatName = (u: User) => `${u.first} ${u.last}`
🧒
In plain wordsPatterns are power tools — great for the right job, silly for the wrong one. Using fancy patterns everywhere makes code more confusing, not less: a giant class that does everything, "managers" wrapping "managers," or a whole factory just to add two numbers. The pro move is using the simplest thing that works, and only reaching for a pattern once you actually hit the problem it solves. Knowing a pattern includes knowing when not to use it.
🧒
सोप्या शब्दांतPatterns म्हणजे power tools — योग्य कामाला उत्तम, चुकीच्याला मूर्खपणा. फॅन्सी patterns सगळीकडे वापरणं code अधिक गोंधळाचं करतं, कमी नाही: सगळं करणारी भली-मोठी class, 'managers' गुंडाळणारे 'managers', किंवा दोन आकडे जोडायला अख्खा factory. Pro move म्हणजे चालणारी सर्वात साधी गोष्ट वापरणं, आणि तो सोडवणाऱ्या समस्येला खरंच भेटल्यावरच एका pattern कडे जाणं. Pattern जाणण्यात ते कधी न वापरायचं हेही येतं.
🌍
In the wildEnterprise Java's AbstractSingletonProxyFactoryBean became a meme for over-abstraction; "FizzBuzz Enterprise Edition" satirizes pattern fever; "distributed monolith" is microservices misapplied; and countless codebases carry God *Service/*Manager classes and interface-per-class layers no one needed. The reaction — Go's simplicity ethos, "YAGNI," "rule of three" before abstracting — is the industry course-correcting against pattern overuse.
🌍
प्रत्यक्ष वापरातEnterprise Java चं AbstractSingletonProxyFactoryBean over-abstraction चं meme झालं; 'FizzBuzz Enterprise Edition' pattern-ताप वर व्यंग करतं; 'distributed monolith' म्हणजे गैरलावलेले microservices; आणि असंख्य codebases God *Service/*Manager classes आणि interface-per-class स्तर वाहतात. प्रतिक्रिया — Go चं simplicity ethos, 'YAGNI,' abstract करण्याआधी 'rule of three' — म्हणजे उद्योग pattern अति-वापराविरुद्ध मार्ग सुधारतोय.

✅ Do

  • Start simple; refactor toward a pattern when the problem actually appears
  • Apply the "rule of three" — abstract after the third real repetition, not the first
  • Split God objects; delete pass-through layers that add nothing

✅ हे करा

  • साधं सुरू करा; समस्या खरंच आली तेव्हा एका pattern कडे refactor करा
  • 'rule of three' लावा — पहिल्या नव्हे, तिसऱ्या खऱ्या पुनरावृत्तीनंतर abstract करा
  • God objects तोडा; काहीच न जोडणारे pass-through स्तर काढा

❌ Don't

  • Add a pattern to show you know it, or "in case we need it later" (YAGNI)
  • Wrap every class in an interface it will never have a second implementation of
  • Let one class or one mediator/facade become the God object again

❌ हे टाळा

  • तुम्हाला माहीत आहे दाखवायला किंवा 'नंतर लागेल म्हणून' pattern जोडू नका (YAGNI)
  • कधीच दुसरी implementation न येणाऱ्या प्रत्येक class ला interface मध्ये गुंडाळू नका
  • एका class किंवा mediator/facade ला पुन्हा God object होऊ देऊ नका
Gotcha: the deepest lesson of this whole playbook — patterns are a means, not a scorecard. The goal was always code that's easy to understand and change safely; a pattern that doesn't serve that goal is just cost. Over-engineering (needless abstraction, premature patterns) is as real a failure as under-engineering (spaghetti, God objects), and it's the more seductive one because it looks sophisticated. Calibrate to the actual problem: know all 28 of these so you can recognize and name them — and have the judgment to reach for one only when its problem genuinely shows up. Simplicity is the default; patterns earn their place.
अडचण: या संपूर्ण playbook चा सर्वात खोल धडा — patterns हे साधन आहेत, scorecard नाही. ध्येय नेहमी समजायला आणि सुरक्षितपणे बदलायला सोपा code होतं; ते ध्येय न साधणारा pattern फक्त खर्च. Over-engineering (अनावश्यक abstraction, अकाली patterns) under-engineering (spaghetti, God objects) इतकंच खरं अपयश, आणि ते अधिक मोहक कारण ते sophisticated दिसतं. खऱ्या समस्येशी calibrate करा: या सगळ्या 28 ना ओळखायला आणि नाव द्यायला जाणा — आणि त्यांची समस्या खरंच आली तरच एका कडे जायचा विवेक ठेवा. Simplicity default आहे; patterns त्यांची जागा कमवतात.

🎯 Check yourself — 5 per round

🎯 स्वतःला तपासा — प्रति फेरी 5

Short rounds beat one giant quiz: answer five, see your score, go again. Progress is saved on this device, so you can leave and come back.

एका मोठ्या quiz पेक्षा लहान फेऱ्या बऱ्या: पाच उत्तरं द्या, score पाहा, पुन्हा जा. प्रगती या device वर सेव्ह होते, त्यामुळे जाऊन परत येऊ शकता.

Round 1 · answered 0/28

🏆 You've been through all 28

🏆 तुम्ही सर्व 28 पूर्ण केले

Every question answered. Reset to run the rounds again, or scroll up and expand any card to review.

प्रत्येक प्रश्न सुटला. पुन्हा फेऱ्यांसाठी reset करा, किंवा वर जाऊन कुठलंही card उघडून उजळणी करा.