In Domain-Driven Design, what is the Specification pattern, and what problem does it solve compared to scattering `if` conditions across your codebase?
answer
- isSatisfiedBy(candidate)
- one rule, one home
- predicate as domain object
- vs inline if scattering
basics
~20 sA Specification is an object that holds a business rule ("is this order overdue?") and can answer yes/no for any candidate. Instead of copy-pasting the same if-check in five places, you write it once as an object and reuse it everywhere.
solid answer
~30 sA Specification wraps a predicate - "does this candidate satisfy this business rule?" - as a first-class domain object with a method like `isSatisfiedBy(candidate)`. It solves the problem of business rules being duplicated and scattered as inline conditionals across services, controllers, and queries. By naming the rule (e.g., `OverdueInvoiceSpecification`) and giving it a single home, you get a reusable, testable, composable unit that speaks the ubiquitous language, instead of the same boolean logic drifting out of sync in multiple places.
go deeper
Should be able to state that a Specification names and encapsulates a business rule as a reusable object, and give a plausible reason (avoiding duplicated ifs) without needing to discuss composition or query translation yet.
Should articulate the isSatisfiedBy contract, give a concrete named example, and recognize the reuse/testability motivation as the core driver, not just 'it's a pattern.'
Should weigh when NOT to use it (one-off checks), distinguish it from a private helper method, and connect it to composability and to repository query translation as the pattern's real payoff.
Should discuss it as a modeling decision that trades indirection for a single source of truth on a business rule, and know the over-application failure mode (Specification-itis) as a real design smell to push back on in review.
## What a Specification is The Specification pattern, as popularized by **Eric Evans** and **Martin Fowler**, takes a boolean business rule — "is this candidate valid/selected/eligible?" — and turns it into an object rather than leaving it as inline code. Mechanically, a Specification exposes one core operation, commonly named `isSatisfiedBy(candidate): boolean`, which takes a domain object and returns whether it matches the encapsulated rule. For example, instead of writing `if (order.status == PAID && order.dueDate.isBefore(today))` inline in three different services, you create an `OverdueInvoiceSpecification` class whose `isSatisfiedBy(order)` method contains that exact expression once, given a name that reads as domain language. ## The problem it solves Why does this exist? Business rules that determine "is this a good candidate" tend to multiply and drift. A junior team member adds an order-eligibility check in the checkout flow; six months later, someone else needs the same rule for a nightly batch report and reimplements it slightly differently (maybe forgetting a currency check, or using `<=` instead of `<`). Now there are two divergent definitions of "eligible order" living in the codebase, and nobody notices until a bug report surfaces the discrepancy. The Specification pattern fixes this by giving the rule exactly **one home**: - one class - one name - one test suite Every consumer — validation code, filtering code, UI enablement logic — depends on that one object instead of re-deriving the logic. ## The trade-off The trade-off is **indirection**. A single `if` statement is trivial to read in place; a Specification adds a class, a constructor, and a method call, which is overkill for a truly one-off check used in exactly one place. The pattern earns its keep specifically when a rule: - **(a)** is reused in more than one place; - **(b)** needs to be composed with other rules; - **(c)** needs to be tested in isolation from the surrounding workflow; - **(d)** needs to be passed around as data (e.g., handed to a repository to select matching records). If none of those apply, wrapping a boolean expression in a class is needless ceremony and adds a layer of indirection a reader has to jump through to understand what's actually being checked. ## Failure modes Failure modes show up in two directions. - **First, under-application:** teams write ad hoc booleans everywhere and the rule genuinely does drift, producing subtle correctness bugs where "overdue" means something different in the billing job than in the customer-facing dashboard — exactly the problem the pattern targets. - **Second, over-application:** teams wrap every trivial boolean check into its own Specification class "because DDD," producing an explosion of tiny classes (`IsPositiveAmountSpecification`, `HasNonNullEmailSpecification`) that add navigation overhead without adding reuse or composability — readers now have to open three files to understand one condition that used to be a single readable line. The sweet spot is rules that are genuinely part of the domain's vocabulary (an underwriter's "is this applicant insurable" rule, a marketplace's "is this listing eligible for boost") and that get combined or reused, not utility-level null checks. ## A worked example A concrete, well-known scenario: an e-commerce platform's promotions engine needs to know whether a cart qualifies for free shipping. The rule — "cart total exceeds $50 AND the customer is in a supported region AND no item in the cart is oversized" — is genuinely business logic, changes over time as marketing adjusts the threshold, and is consulted from multiple places: - the cart page (to show a progress bar) - the checkout flow (to actually apply free shipping) - a nightly analytics job (to measure how many carts nearly qualified) Wrapping this as a `FreeShippingEligibilitySpecification` gives the business rule one authoritative implementation, one place to update when the threshold changes from $50 to $75, and one thing product/QA can point to when asking "why didn't this order get free shipping?" Contrast this with a specification wrapping `amount > 0` used exactly once in a form validator — here a plain `if` is clearer and the class only adds friction. ## What it is not It's worth being precise about what the Specification pattern is not: it is not the same as the generic Gang-of-Four "Strategy" or "Composite" pattern, even though it's frequently implemented using both (composability via Composite, pluggable rule objects via Strategy). It is a **domain-modeling idiom** specifically about naming and encapsulating a business predicate so it can be validated against, selected by, and eventually translated into a query, which is what distinguishes it from a bare predicate function or lambda: the Specification is a first-class, named, potentially serializable domain concept, not just a closure.
- Would you write a Specification for a check used in exactly one place and never reused?Generally no - the value of a Specification comes from reuse, composability, or the need to test/pass the rule independently of its call site. A one-off boolean is clearer as an inline `if`; wrapping it in a class adds indirection without buying anything, and a reviewer now has to jump to another file to see a single comparison.
- How is a Specification different from just extracting the condition into a private method?A private method is scoped to one class and can't be composed, passed around, or reused elsewhere without copy-pasting. A Specification is a standalone object, so it can be combined with AND/OR/NOT, stored, handed to a repository, or unit-tested in isolation - none of which a private helper method supports.
- Does the rule inside a Specification have to be pure/side-effect-free?Yes, in practice `isSatisfiedBy` should be a pure predicate - it evaluates the candidate and returns a boolean without mutating state or performing I/O. Side effects would make the Specification unsafe to reuse across validation, selection, and query-translation contexts, since those contexts may call it differently (e.g., translate it to SQL rather than execute it).
A Specification is like a bouncer's guest list rule written on an index card ("must be 21+ and on the list") - instead of every doorman at every entrance memorizing and possibly misremembering the rule, they all check the same card.
saying these in an interview costs you the question
- Wraps every trivial null-check or `> 0` comparison in its own Specification class
- Can't explain why the rule needs to be an object instead of a boolean method
- Confuses Specification with the generic GoF Strategy pattern with no domain framing
- Puts I/O or persistence calls inside isSatisfiedBy
- Treats it as just a fancy validator with no composability need