skip to content

Tell, Don't Ask

Instead of pulling state out of an object and deciding for it, tell the object what you want and let it decide. This is the cure for feature envy and anemic models, and it explains why some code ends up with all the data in one place and all the logic in another.

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

questions

6

What does the design principle "Tell, Don't Ask" mean, and what problem is it meant to prevent?

level: juniorimportance: must knowfreq 62%

answer

  1. tell object what, don't ask for state
  2. decision follows the data
  3. getter + branch on same object = smell
  4. invariants need a single mutation route
  5. not a ban on return values

basics

~20 s

Instead 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 s

Tell, 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
pseudocode
// 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 | Frozen

go deeper

for a junior

Define it in one sentence and give the account/withdraw example; state that it keeps behavior with data.

for a middle

Add the concrete costs of ask style - duplicated rules, unenforceable invariants, representation locked by accessors - and show the refactoring move.

for a senior

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.

for a principal

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

context

open as a page

How do the "feature envy" code smell and the "anemic domain model" relate to Tell, Don't Ask, and how would you refactor a case of each?

level: middleimportance: must knowfreq 48%

basics

~20 s

Both are what ask-style code looks like at scale. Feature envy is one method using another object's data more than its own; anemic model is data-only classes with all logic in services. Fix by moving the logic onto the class that owns the data.

open as a page

When is querying an object's state ("asking") the right design rather than a violation of Tell, Don't Ask? Give concrete categories.

level: middleimportance: should knowfreq 34%

basics

~20 s

Asking is fine when you only need the value, not a decision about that object: displaying or serializing data, reporting and analytics, test assertions, and pure value objects. The smell is asking and then deciding on the object's behalf.

open as a page

Following Tell, Don't Ask tends to produce objects whose methods return little or nothing. How do you keep such designs testable and observable without re-exposing internal state?

level: seniorimportance: should knowfreq 22%

basics

~10 s

Have the object report outcomes instead of state: return a result type, emit a domain event, or call a collaborator you can substitute in tests. Assert on behavior and outcomes rather than on fields.

open as a page

How does Tell, Don't Ask differ from the Law of Demeter, and why does fixing a "train wreck" like `a.getB().getC().doThing()` by adding delegating methods sometimes make the design worse?

level: seniorimportance: should knowfreq 30%

basics

~20 s

The Law of Demeter limits how far you may navigate - only talk to immediate collaborators. Tell, Don't Ask says who should make a decision. Blindly adding pass-through methods satisfies Demeter's letter while still asking, and bloats the middle object.

open as a page

How does Tell, Don't Ask translate to service and API boundaries in a distributed system, and where does it stop applying?

level: principalimportance: nice to knowfreq 16%

basics

~20 s

Across services, telling means sending a command or intent ("reserve these seats") instead of pulling another service's data and deciding for it. That avoids chatty calls and stale decisions - but payloads themselves are still plain data.

open as a page