In Domain-Driven Design, 'conceptual contours' means shaping classes and methods to follow the natural divisions in the domain rather than an engineer's convenience. Given a ShippingCalculator whose single calculate(order, address, weight, isExpress, hasInsurance, discountCode) method has grown unwieldy, how would you use conceptual contours to decide how to split it?
answer
- contours = natural domain seams, not arbitrary splits
- signal: what varies independently over time
- split by concept, not by line count or data source
- too fine = scattered logic; wrong axis = re-split later
- granularity should mirror how the business changes
basics
~20 sInstead of splitting the method by lines of code or by what's easy to test, split it along the real concepts the business already thinks in, like 'base rate' vs 'express surcharge' vs 'insurance fee', because those are the pieces that actually change independently over time.
solid answer
~50 sConceptual contours asks: what are the cohesive sub-concepts inside this operation, and do they vary independently in the real domain? For the shipping calculator, you'd look at how the business actually changes pricing over time -- express surcharges get updated by one team's promotions calendar, insurance fees by a risk team, discounts by marketing -- and that independent variation is the signal for where the seams should be, not arbitrary line counts. You'd likely extract BaseRate, ExpressSurcharge, InsuranceFee, and Discount as small collaborating pieces composed by a thin orchestrating method, each with an intention-revealing name and ideally side-effect-free. The trade-off is that finding the right contour requires real domain knowledge and iteration -- split too finely and you get scattered logic that's hard to trace; split along the wrong axis (e.g. by data source instead of by business concept) and the seams don't match future change, so you'll be re-splitting again soon.
go deeper
Can recognize that a very long method doing several unrelated things should probably be split, though may default to splitting by line count rather than by concept.
Extracts sub-concepts from an overloaded method along business lines when guided, and can name the extracted pieces meaningfully, though may need review to catch a wrong-axis split.
Independently identifies which parts of a model vary independently in the real domain and shapes boundaries around that, and revisits contours when duplicated logic or repeated joint changes reveal the seam was wrong.
Guides a team's ongoing model refactoring by reading change history and stakeholder ownership to spot true conceptual contours across modules, and balances the churn cost of re-splitting against the rigidity cost of leaving a wrong boundary in place.
## What a contour is **Conceptual contours** means shaping the granularity of classes and methods -- how finely or coarsely you split responsibilities -- to match the natural divisions inside the domain itself, rather than splitting by convenience metrics like line count, file size, or 'this felt too long.' The practical mechanism is to look for sub-concepts inside an operation that vary independently over time or are owned by different stakeholders, and to carve the boundary exactly there. For a `ShippingCalculator.calculate(order, address, weight, isExpress, hasInsurance, discountCode)` method that has grown unwieldy, that means identifying which parts of the calculation are actually separate rules in the business's mind, and extracting each as its own small, well-named, ideally side-effect-free piece, composed by a thin orchestrating method that just calls each in turn: - a base rate table - an express surcharge - an insurance fee - a discount policy ## Getting the granularity wrong in either direction This exists because getting the granularity wrong in either direction produces rigidity. | Direction | What it costs | |---|---| | **Split too coarsely**, as in the original giant `calculate` method | every change, even one that logically belongs to only the discount rule, risks touching unrelated code and requires understanding the whole method to make a safe edit | | **Split too finely**, or along an axis that doesn't correspond to how the business actually varies | logic ends up scattered across many tiny pieces that must all be traced together to understand one real business rule, which is just as confusing in the other direction | Conceptual contours gives a concrete, non-arbitrary answer to 'how big should this piece be': as big as one thing that changes together, no bigger, no smaller -- which is what keeps a model malleable as understanding and requirements evolve, the entire goal of Supple Design. ## Contours are discovered, not guessed Finding the right contour requires actual domain knowledge, which usually isn't available upfront -- you can't reliably guess correct boundaries in a greenfield module on day one. That means the practical trade-off is between: - **refactoring iteratively**, accepting that early boundaries will be wrong and need revision at some churn cost; - **versus over-designing upfront** with speculative splits based on guesses about future variation, which frequently guesses wrong and produces unnecessary abstraction layers nobody needed. This discovery process is ongoing: contours found today may be wrong once you learn more, and a healthy team treats re-splitting a boundary as normal maintenance, not as a sign the first design was a failure. ## Symptoms of a wrong contour - The most common symptom of a missing or wrong contour is **duplicated logic**: if a team splits shipping calculation by 'domestic vs international' instead of by rate/surcharge/discount concept, both branches end up with nearly identical copies of the discount logic, because discount rules vary independently of the domestic/international axis. Every future discount-rule change then has to be made in two places, and it's easy for the copies to drift apart silently, producing a bug where domestic and international orders apply discounts inconsistently. - Another symptom is a recurring pattern where two 'unrelated' pieces of logic living in the same method or class keep requiring careful, deliberate edits to avoid breaking each other -- that repeated friction is a strong signal the concepts inside actually vary independently and belong on opposite sides of a seam that doesn't currently exist. ## The shipping example, resolved Concretely for the shipping example: after noticing that changing the discount-code logic and changing the insurance-fee logic are done by different people, on different schedules, and never actually depend on each other's internals, the team extracts each into its own class or pure function, each individually unit-testable with simple input/output examples, with no need to construct a full `Order` with an address and weight just to test a discount percentage calculation. The orchestrating `ShippingCalculator.calculate()` becomes a short, readable composition of these pieces, and, crucially, the next time marketing changes how discount codes stack, only the `Discount` piece needs to change, with no risk to the insurance-fee math sitting nearby, which is exactly the reduction in ripple effect that finding the right conceptual contour is meant to deliver.
- How do you find the 'right' contour when the domain isn't obvious yet, e.g. a greenfield module?You typically can't know it upfront with confidence -- this comes from iterative refactoring as understanding deepens, often triggered by the pain of a change that touches too much or a bug caused by two concerns being entangled. Watching what actually changes together over a few sprints, and talking to domain experts about which rules are 'the same rule' versus 'coincidentally similar,' is more reliable than guessing the seams on day one.
- What's the difference between conceptual contours and just applying single-responsibility principle?SRP says a class or method should have one reason to change, but it doesn't tell you what that reason is or where the boundary sits -- conceptual contours supplies the domain-specific answer by anchoring the boundary to how the business actually varies, such as pricing rules owned by different teams, rather than a generic technical heuristic like line count or coupling metrics alone.
- What happens if you split along the wrong axis, say by 'domestic vs international shipping' instead of by rate/surcharge/discount concept?You'd end up duplicating the discount and insurance logic inside both the domestic and international branches, since those concerns vary independently of the domestic/international axis; a change to how discounts are calculated then has to be made in two places, which is precisely the rigidity Supple Design is trying to prevent.
Like carving a chicken along its joints instead of hacking straight across the bone -- the joints (contours) are where the animal's structure naturally comes apart cleanly; cutting elsewhere either takes more force or leaves you with pieces that don't correspond to anything useful.
saying these in an interview costs you the question
- splitting a method purely by line count or file size
- boundaries chosen by data source or technical layer instead of business concept
- duplicated logic appearing in two 'siblings' that split along the wrong axis
- treating the first refactor as final instead of expecting contours to be revised as understanding deepens
- no domain-expert input into where a class/method boundary sits