skip to content

Patterns Meta & Practice

Questions about patterns themselves rather than any one pattern: how to choose, how to recognise misuse, how patterns differ from language idioms, and how to arrive at a pattern by refactoring.

part ofSoftware design & architectureoverview, primer and where to startread it →
on this pageshow

questions

23

What is the "god object" anti-pattern in software design, and why is it considered harmful?

level: juniorimportance: must knowfreq 62%

answer

  1. one class, all responsibilities
  2. low cohesion + high coupling
  3. top of the git-churn list
  4. tests need the whole world
  5. extract by field clusters, delegate

basics

~20 s

A god object is one class that knows and does almost everything: data, rules, coordination, I/O. Because everything depends on it and every feature edits it, it is hard to understand, hard to test, and a constant source of conflicts and bugs.

solid answer

~50 s

A god object (a.k.a. god class or "the blob") is a single unit that has accumulated responsibilities from many unrelated concerns. Its symptoms are low cohesion (methods operate on disjoint subsets of its fields, so there is no single reason for it to exist) and high coupling (most of the system either calls into it or is called from it). The costs are concrete: you cannot understand a change locally, every feature touches the same file so teams collide in merges, unit tests need huge setup because instantiating it drags in every dependency, and a bug in one concern can corrupt state used by an unrelated concern. It is the practical violation of the Single Responsibility Principle. The cure is incremental: add characterization tests, find clusters of fields used together, extract those clusters into focused classes, then delegate — never a big-bang rewrite.

go deeper

for a junior

Define it (one class doing too much), name one concrete pain (hard to test / everyone edits it), and mention the Single Responsibility Principle.

for a middle

Add the cohesion/coupling vocabulary, list recognition signals (field clusters, churn, fan-out), and describe extract-class refactoring with delegation.

for a senior

Discuss why it becomes a team coordination bottleneck, why big-bang rewrites fail, characterization tests, interface segregation for callers, and lint/architecture guardrails against regrowth.

for a principal

Frame it as an organizational and risk problem: change amplification, ownership, strangler migration sequencing, measuring progress (churn, coupling metrics, incident rate), and deciding when tolerating it is the correct economic call.

## The name **God object** (also *god class*, *blob*, *kitchen-sink class*) is a design anti-pattern: a recurring "solution" that looks convenient in the moment but reliably makes the system worse. The class is called a *god* object because it knows about, and controls, nearly everything in the program. ## Two definitions you need first - **Cohesion** — how strongly the parts of one unit belong together. High cohesion: every method uses the same handful of fields toward one purpose. Low cohesion: method group A touches fields `a1,a2`, group B touches `b1,b2`, and the two groups never meet — that class is really two classes stapled together. - **Coupling** — how much other code depends on this unit (incoming/afferent) and how much it depends on others (outgoing/efferent). A god object typically has both very high. A god object is therefore the extreme point of **low cohesion + high coupling**, which is the opposite of the classic design goal ("high cohesion, low coupling"). It is the most visible violation of the **Single Responsibility Principle (SRP)** — the idea that a unit should have one reason to change. ## How to recognize one (signals, not proof) 1. **Size**: hundreds or thousands of lines; dozens of fields; dozens of public methods. 2. **Disjoint field usage**: you can partition its methods into groups that touch non-overlapping fields. (Formalized by cohesion metrics such as *LCOM*, "lack of cohesion of methods".) 3. **Import/dependency fan-out**: it references the database, the HTTP layer, the domain rules, formatting, caching, logging config… 4. **Git churn**: it is the top file in "most frequently changed" and the top source of merge conflicts. Every unrelated feature branch edits it. 5. **Naming smell**: `SystemManager`, `AppHelper`, `Utils`, `DataProcessor` — vague nouns that cannot be violated by any addition, so everything gets added. 6. **Test setup pain**: constructing it in a unit test requires mocking eight collaborators you do not care about. ## Why it actually hurts - **No local reasoning.** To change one behavior you must convince yourself you did not break the other twelve behaviors sharing its mutable state. - **Change amplification and collisions.** Because it is the intersection point of all features, unrelated teams serialize on it (a coordination bottleneck, not just a code smell). - **Untestability.** Its dependencies are transitive: instantiate it and you drag in the whole system. Tests become slow integration tests or don't get written. - **Hidden invariants.** Shared mutable fields create implicit ordering rules ("you must call `init()` before `render()`") that no type or signature expresses. - **Blocked reuse.** You cannot reuse the 5% you want without taking the other 95%. - **Concurrency hazard.** One big mutable state blob is where data races congregate. ## When a big class is *not* a god object - A **façade** or **API surface** that only *delegates* to focused collaborators can legitimately be wide but is thin and stateless — width alone is not the sin; hoarding *state and unrelated logic* is. - **Generated code**, tables of constants, and exhaustive mapping/serialization code can be long yet perfectly cohesive. - In a 200-line script, one class doing everything is fine. Anti-patterns are context-dependent; the cost only appears with scale, team size, and lifetime. ## How to unwind one (incrementally) 1. **Pin behavior first** with *characterization tests* — tests that assert what the code currently does, so a refactor that changes behavior fails loudly. 2. **Find the seams**: cluster methods by which fields they touch; each cluster is a candidate class. 3. **Extract class / move method** one cluster at a time; leave the original methods as one-line delegations so callers keep compiling. 4. **Narrow the interfaces**: give each caller only the operations it needs (Interface Segregation), which shrinks incoming coupling. 5. **Invert dependencies**: pass collaborators in (constructor injection) rather than having the class construct or look them up. 6. **Strangle**: route new features to the new focused classes; let the god object shrink until it can be deleted. 7. **Guardrails**: a size/complexity lint rule or an architecture test so it does not regrow. The key discipline is that the refactor is a sequence of small, individually shippable, behavior-preserving steps. Big-bang rewrites of god objects fail because the god object is precisely the place where undocumented business rules were hiding.

  • Is a large class always a god object?
    No. Size is a signal, not the definition. A long but cohesive class (a mapping table, generated code, or a thin façade that only delegates) is fine. The defining traits are low cohesion — methods operating on unrelated field subsets — plus high coupling to the rest of the system.
  • What is the first thing you do before splitting one?
    Write characterization tests that lock in current observable behavior, because a god object usually hides undocumented business rules. Only then extract classes in small, behavior-preserving steps, keeping delegating methods so existing callers keep working.

A kitchen where one enormous drawer holds cutlery, batteries, tax paperwork and the spare keys. Nothing is lost exactly, but every person needing anything must open the same drawer, rummage past everyone else's stuff, and any reorganization affects the whole household.

saying these in an interview costs you the question

  • "Any class over N lines is a god object" — size is a heuristic, not the definition
  • "We'll fix it with a full rewrite next quarter" — big-bang rewrites lose undocumented rules
  • Calling a stateless delegating façade a god object
  • Splitting it purely by file size instead of by cohesive field/behavior clusters
  • Refactoring it with no tests pinning current behavior

context

open as a page

What is the difference between a design pattern and a language idiom?

level: juniorimportance: must knowfreq 55%

basics

~20 s

A design pattern is a language-neutral solution shape for a recurring design problem, like Observer or Strategy. An idiom is a small, language-specific way of writing something well, like C++ RAII or Python comprehensions. Patterns travel between languages; idioms usually do not.

open as a page

What does "refactoring to patterns" mean, and why is introducing a design pattern through refactoring often preferred over choosing patterns up front?

level: juniorimportance: must knowfreq 58%

basics

~20 s

It means you don't pick patterns before writing code. You write the simplest thing that works, and when the code starts to smell (duplication, a growing conditional), you refactor in small behavior-preserving steps until a known pattern emerges. The smell justifies the pattern.

open as a page

How should you decide which design pattern to use in a given situation, and why do experienced engineers insist you start from the problem rather than from the pattern catalogue?

level: juniorimportance: must knowfreq 62%

basics

~20 s

Start from the concrete problem: what varies, what must stay stable, what change you expect. Then look for a pattern whose stated intent restates that problem. Choosing a pattern first and bending code to fit it adds complexity without solving anything.

open as a page

Why is heavy use of the Singleton pattern (a class exposing one globally reachable shared instance, typically via a static accessor) often treated as an anti-pattern?

level: middleimportance: must knowfreq 68%

basics

~20 s

Because a singleton is global mutable state with a static access point. Callers reach it directly instead of declaring it, so dependencies are hidden, tests share and leak state between each other, substitution for fakes is hard, and lifetime/threading rules become implicit.

open as a page

How do design patterns differ from architectural patterns, and why does the distinction matter when you choose one?

level: middleimportance: must knowfreq 48%

basics

~20 s

Design patterns organise a few classes or functions inside a module (Strategy, Observer, Decorator). Architectural patterns organise whole systems — modules, processes, services (layered, hexagonal, event-driven, microservices). Design patterns are cheap to change; architectural ones are expensive and constrain deployment and teams.

open as a page

Which code smells motivate the refactorings "Replace Conditional Logic with Strategy" and "Replace State-Altering Conditionals with State", and how do you decide which of the two patterns the code actually wants?

level: middleimportance: must knowfreq 62%

basics

~20 s

Both start from big if/switch chains. Use Strategy when the branches are interchangeable ways to compute the same thing chosen by the caller or config. Use State when the branches depend on an object's current mode and the branches also decide the next mode.

open as a page

Strategy, State, and Command all end up as an object holding behaviour behind a small interface. Given that near-identical structure, how do you decide which one a problem calls for?

level: middleimportance: must knowfreq 58%

basics

~20 s

By intent, not shape. Strategy swaps interchangeable algorithms chosen from outside. State encapsulates behaviour that changes with an object's lifecycle stage, and states decide the next state. Command turns a request into an object you can queue, log, retry, or undo.

open as a page

Which classic Gang of Four patterns collapse into a one-liner in a language with first-class functions, and what exactly is lost or kept when they do?

level: seniorimportance: must knowfreq 45%

basics

~20 s

Strategy, Command, Template Method hooks, simple Factories and much of Observer collapse: instead of an interface plus an implementing class, you pass a function or lambda. The intent survives — varying behaviour is still injected — but the ceremony (extra types, boilerplate) disappears.

open as a page

How do you make the cost/benefit case for introducing a design pattern — what concrete trade-offs (coupling, extensibility, complexity) do you weigh, and how do you decide it is not worth it?

level: seniorimportance: must knowfreq 48%

basics

~20 s

A pattern buys cheap change along one axis and charges indirection everywhere. Weigh how likely that change is, how many new types and hops you add, whether coupling direction improves, and whether tests get simpler. If you cannot name the change it makes cheap, skip it.

open as a page

What is premature abstraction (also called speculative generality), how does it relate to YAGNI, and how do you decide when an abstraction has earned its place?

level: middleimportance: should knowfreq 48%

basics

~20 s

Premature abstraction is adding interfaces, factories or configuration for requirements you only imagine. YAGNI — "You Aren't Gonna Need It" — says don't build it until a real need exists, because guessed abstractions usually fit the wrong axis and still cost reading and change effort.

open as a page

The Iterator pattern is a Gang of Four design pattern, yet many languages provide generators or comprehensions built in. When a language feature subsumes a pattern, what changes and what does not?

level: middleimportance: should knowfreq 30%

basics

~20 s

The problem stays the same — traverse a collection without exposing its internals. What changes is the cost: instead of hand-writing an iterator class with hasNext/next, you write a generator or comprehension. Writing the class version anyway is unidiomatic and adds state and bugs.

open as a page

What smell does the refactoring "Form Template Method" address, what are its mechanical steps, and what are its main drawbacks?

level: middleimportance: should knowfreq 38%

basics

~20 s

It targets sibling subclasses whose methods do the same steps in the same order but differ in a few step bodies. You make the steps identical in shape, pull the shared sequence up into the superclass as one method, and leave the differing steps as overridable hooks.

open as a page

Adapter, Decorator, Proxy, and Facade all wrap one object (or several) behind another object. What distinguishes them, and how do you pick between them when you are about to introduce a wrapper?

level: middleimportance: should knowfreq 50%

basics

~20 s

By what the wrapper does to the interface and the behaviour. Adapter changes the interface to one the client expects. Decorator keeps the same interface and adds behaviour, stackably. Proxy keeps the same interface and controls access (lazy, remote, caching, permission). Facade offers a simpler interface over a whole subsystem.

open as a page

Compare the Service Locator pattern (classes ask a global registry for their collaborators at runtime) with dependency injection. Why do many architects label Service Locator an anti-pattern, and when is it still justified?

level: seniorimportance: should knowfreq 45%

basics

~20 s

With a service locator, a class calls a shared registry to fetch what it needs, so its dependencies are hidden inside method bodies. With dependency injection, they arrive through the constructor, so they are visible, required at construction, and easy to replace in tests.

open as a page

C++ RAII, Java try-with-resources, C# using, Go defer and Python context managers all release resources safely. Are these the same design pattern or five different idioms?

level: seniorimportance: should knowfreq 32%

basics

~20 s

They are five language idioms serving one shared intent: release a resource on every exit path, including errors. The intent — scope-bound resource management — is the portable idea; RAII, try-with-resources, using, defer and with-statements are how each language spells it.

open as a page

When does the refactoring "Move Embellishment to Decorator" apply, how do you perform it, and what are its limits compared with adding another subclass or another conditional?

level: seniorimportance: should knowfreq 32%

basics

~20 s

Apply it when a class's core job is buried under optional extras controlled by flags — logging, caching, compression, discounts. You extract each extra into a wrapper class that implements the same interface, delegates to the wrapped object, and adds its bit. Callers compose only the extras they need.

open as a page

What does "refactoring away from a pattern" mean, what signals tell you a pattern is over-applied, and how would you safely remove one from a live codebase?

level: seniorimportance: should knowfreq 30%

basics

~20 s

It means removing a pattern that costs more than it gives — for example a Strategy interface with one implementation or an unnecessary Singleton. You inline the indirection step by step, with tests green, until the simpler direct code remains.

open as a page

You inherit a business-critical, 6,000-line god class that every team edits weekly and that has almost no tests. How do you plan and sequence unwinding it without freezing feature delivery?

level: principalimportance: should knowfreq 28%

basics

~20 s

Don't rewrite it. Pin current behavior with characterization tests, find seams where responsibilities cluster, extract one cohesive piece at a time behind a delegating façade, route new work to the extracted parts, and add guardrails so the old class cannot regrow.

open as a page

How do you judge whether a construct is genuinely an anti-pattern rather than a legitimate pattern used in a context you're unfamiliar with?

level: seniorimportance: nice to knowfreq 22%

basics

~20 s

Judge by consequences in context, not by name. An anti-pattern is a common solution whose costs reliably outweigh its benefits and for which a better alternative exists. If you can't state the concrete harm here and a cheaper alternative, it's just an unfamiliar choice.

open as a page

How do you evaluate the claim that design patterns are just workarounds for missing language features, and what does that mean for standards in a polyglot organisation?

level: principalimportance: nice to knowfreq 20%

basics

~20 s

Partly true: some patterns exist only because a language lacked a feature (Strategy without first-class functions, Visitor without pattern matching) and vanish once it gains one. But patterns also name problems and trade-offs, which no feature removes. Treat them as shared vocabulary, not code templates.

open as a page

As a technical leader, how do you decide when a design should be reached through incremental refactoring to patterns versus committed to up front, and how do you keep a team from either pattern-happiness or abstraction paralysis?

level: principalimportance: nice to knowfreq 18%

basics

~20 s

Decide by reversibility and blast radius. Decisions you can change later inside one codebase (class structure) should emerge from refactoring. Decisions that are expensive to undo — data models, public contracts, service boundaries, security — deserve up-front design. Teams need tests and a norm that abstractions must be earned and can be removed.

open as a page

Patterns rarely appear alone. How do you reason about combining patterns in one design — which combinations are natural, which signal a modelling mistake, and how do you keep a large codebase's pattern usage coherent?

level: principalimportance: nice to knowfreq 30%

basics

~20 s

Combine patterns when each answers a different question — for example a factory choosing a strategy, or a composite of commands. It is a warning sign when several patterns solve the same question, when wiring code outweighs domain code, or when nobody can describe the runtime object graph.

open as a page