What does the design principle "Tell, Don't Ask" mean, and what problem is it meant to prevent?
answer
- tell object what, don't ask for state
- decision follows the data
- getter + branch on same object = smell
- invariants need a single mutation route
- not a ban on return values
basics
~20 sInstead of reading an object's data and deciding what to do outside it, ask the object to do the job and let it decide using its own data. Behavior stays next to the data it needs.
solid answer
~40 sTell, Don't Ask says: when a decision depends on data an object owns, put the decision inside that object instead of exporting the data to a caller. Rather than `if (account.getBalance() >= amount) account.setBalance(account.getBalance() - amount)`, you write `account.withdraw(amount)` and let the account enforce its own rule. It prevents leaked responsibility: when callers query state and branch on it, the same rule is duplicated in every caller, an invariant (a rule that must always hold, e.g. "balance never goes negative") can be broken by any caller that forgets the check, and the getter/setter pairs freeze the internal representation so it cannot change without touching everyone. It is a heuristic for real encapsulation - hiding data and the decisions made from it - not a ban on ever returning values.
code
pseudocode · 7 lines// ASK: rule lives in the caller, duplicated everywhere
if (account.getBalance() >= amount && !account.isFrozen()) {
account.setBalance(account.getBalance() - amount);
}
// TELL: rule lives with the data, enforced once
account.withdraw(amount) // -> Ok | InsufficientFunds | Frozengo deeper
Define it in one sentence and give the account/withdraw example; state that it keeps behavior with data.
Add the concrete costs of ask style - duplicated rules, unenforceable invariants, representation locked by accessors - and show the refactoring move.
Frame it as the operational test for encapsulation, name where it does not apply (DTOs, read models, serialization), and discuss the god-object counterweight from single responsibility.
Position it as a boundary-design heuristic: which side of an interface owns a decision, how that shapes write models vs read models, and the organizational cost when rules leak into callers across teams.
## Terms first - **Object**: data (fields/state) plus behavior (methods that act on that state). - **Getter/accessor**: a method that just hands out a field (`getBalance()`). **Setter/mutator**: one that just overwrites a field (`setBalance(x)`). - **Encapsulation**: hiding internals so outside code depends on what a component *does*, not how it stores things. A class with a getter and setter for every field has the *syntax* of encapsulation but none of the substance - the fields are effectively public with extra typing. - **Invariant**: a condition that must be true of an object at all times (balance >= 0; an order has at least one line; a state machine is in exactly one legal state). ## The rule Coined in the Pragmatic Programmer/`c2` wiki tradition: *procedural code gets information then makes decisions; object-oriented code tells objects to do things.* Concretely - if the code you are about to write is `x.getSomething()` followed by a decision **about x**, that decision probably belongs on `x`. **Ask style** ``` if (order.getStatus() == DRAFT && order.getLines().size() > 0) { order.setStatus(SUBMITTED); order.setSubmittedAt(now()); } ``` **Tell style** ``` order.submit(clock); // throws or returns a result if not submittable ``` ## Why it matters 1. **Single point of truth for a rule.** With ask style, every caller re-implements "can this be submitted?". Add a third condition later and you must find all of them. With tell style there is one method to change. 2. **Invariants become enforceable.** An object can only guarantee balance >= 0 if the only route to changing the balance is a method it controls. Public setters make every guarantee advisory. 3. **Freedom to change representation.** If nobody outside sees `status` and `submittedAt`, they can become an event list, a state object, or a computed field. Getters for every field are a published contract on the *shape of the data*, which is the hardest thing to change later. 4. **Cohesion and readability.** Behavior lives where the data lives, so a reader learns the domain from the domain type rather than from scattered service code. ## What it is not - It is **not** "getters are forbidden". Rendering, serialization, reporting and assertions legitimately need values. The rule targets `get` + *decision about the same object*, not `get` + *display*. - It is **not** "methods must return void". Returning a result (a new value, a Result/Either, a domain event) is fine and often better than mutating in place. - It is **not** a law. Data-transfer objects, configuration records, value objects and boundary payloads are deliberately data-only. ## Trade-offs and edge cases - **Where multiple objects' data is needed**, no single object can decide alone; the decision belongs to a coordinating object (a domain service, a policy) that is *told* to do the work - not to a random caller. - **Framework/persistence constraints** sometimes force public accessors (ORMs, serializers). Prefer package-private/field access, mapping layers, or accepting the accessors while keeping decisions inside. - **Over-telling** produces god objects: an entity that grows every workflow, notification and formatting concern because "it owns the data". The counterweight is single responsibility - move the *policy* out into a collaborator the object is told to use, don't move the *state check* out. - **Layers matter.** Query/read models are ask-shaped by design; command/write models should be tell-shaped, because that is where invariants live. ## How to apply it in review Scan for the shape `if (a.getX() ...) a.setY(...)`. Ask: *what would I name this decision?* Name it, move it onto `a`, delete the accessors that no longer have users. Repeat until the accessors that remain exist for presentation, not decision.
- Does Tell, Don't Ask mean a method may never return a value?No. It targets the pattern of pulling state out to make a decision about that same object. Returning a computed value, a new immutable instance, or an outcome object (Ok/Insufficient) is fully compatible - the decision still happened inside the object.
- How does this principle relate to encapsulation?It is the behavioral test for it. Encapsulation is not achieved by making fields private and adding accessors; it is achieved when outside code cannot make decisions that depend on the internal representation. Tell, Don't Ask is the day-to-day rule that gets you there.
You don't reach into a colleague's desk to check their calendar and then move their meetings; you ask them to book a slot. They know their own constraints, and if their scheduling rules change, nothing on your side changes.
saying these in an interview costs you the question
- Claiming it means "never use getters" or "all methods return void"
- Confusing it with the Law of Demeter (related, but Demeter is about chained navigation, not about who decides)
- Applying it to DTOs, config records and serialization payloads, where data-only is correct
- Moving unrelated concerns (formatting, notification, persistence) onto entities in its name, creating god objects
- Believing private fields plus generated getters/setters already equal encapsulation