skip to content

Command-Query Separation

Meyer's rule that a method either changes state or returns a value, never both, so queries can be called freely without surprises. You will also cover the pragmatic exceptions, such as pop() on a stack, that interviewers like to raise.

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

questions

6

What is Command-Query Separation (CQS), and how do you classify a method as a command or a query?

level: juniorimportance: must knowfreq 58%

answer

  1. Meyer: asking a question must not change the answer
  2. command = effect, no value; query = value, no effect
  3. deletion / repetition / movement test
  4. queries safe in logs, asserts, debuggers, retries
  5. CQS = method level; CQRS = architecture level

basics

~10 s

CQS says every method should either change state (a command, returning nothing) or return information (a query, changing nothing) — never both. Put differently: asking a question must not change the answer.

solid answer

~50 s

Command-Query Separation, coined by Bertrand Meyer, splits every operation into two disjoint kinds. A **command** (procedure) mutates observable state and returns nothing meaningful. A **query** (function/attribute) returns a value and leaves observable state unchanged, so calling it zero, one, or many times makes no difference. The rule is that no operation is both. The practical test: could I delete a call to this method, or call it twice in a row, or reorder it with a log statement, without changing program behaviour? If yes it's a query; if no it's a command. The payoff is reasoning power. Queries are safe to call inside assertions, logs, debuggers, conditions, and retries, and they can be cached or reordered. Commands are the small, explicitly marked set of places where state changes, which makes concurrency, testing, and debugging far easier.

code

pseudocode · 10 lines
pseudocode
// Mixed: returns a value AND mutates -> violates CQS
item = queue.popNext()          // reading also removes

// Separated: a query and a command
item = queue.peekNext()          // query: safe to call twice, safe in a log
queue.removeNext()               // command: the only line that changes state

// Why it matters:
log("next is " + queue.popNext())   // logging silently consumed an item
log("next is " + queue.peekNext())  // logging is harmless

go deeper

for a junior

State the definition and Meyer's slogan, give one example of each kind (list.size() vs list.clear()), and name one benefit such as safe logging.

for a middle

Add the classification tests (delete/repeat/reorder), the observable-vs-benign side-effect distinction, and at least one legitimate exception like stack pop or compare-and-swap.

for a senior

Frame it as the OO restatement of purity, connect it to caching, read replicas, lock granularity and retry safety, and clearly distinguish CQS from CQRS.

for a principal

Discuss it as an enforceable codebase convention: naming and typing schemes that encode the split, how it interacts with atomicity and concurrency, when the atomicity cost outweighs the clarity benefit, and how it scales up into HTTP safe methods and read/write model separation.

## The principle **Command-Query Separation (CQS)** was formulated by **Bertrand Meyer** (creator of the Eiffel language, author of *Object-Oriented Software Construction*). It states: > Every method should either be a **command** that performs an action, or a **query** that returns data to the caller, but not both. Meyer's slogan for it: **"Asking a question should not change the answer."** ### Definitions of the terms used - **Method / operation** — a named unit of behaviour you can invoke (a function, procedure, subroutine, member function). Nothing here is specific to any language. - **State** — the data a program remembers between operations: object fields, global variables, a database row, a file, a counter, the position of a cursor. - **Observable state** — the part of that state that any caller can detect later, through some other operation. This qualifier matters: state that nobody can observe (see the discussion of caches below) is not really state for this purpose. - **Side effect** — any change to observable state (or the outside world: writing a file, sending a network message, printing) that outlives the call. - **Command** (Meyer calls it a *procedure*) — an operation whose purpose is a side effect. Conventionally it returns nothing. Examples: `account.deposit(100)`, `list.clear()`, `session.logout()`. - **Query** (Meyer: *function* or *attribute*) — an operation whose purpose is to compute and return a value, with no side effect. Examples: `account.balance()`, `list.size()`, `user.isActive()`. ### How to classify an operation Apply these three tests. A **query** must pass all of them: 1. **Deletion test** — if I delete the call (and don't use the result), does anything else in the program change? A pure query: no. 2. **Repetition test** — can I call it twice in a row instead of once, with no difference? A pure query: yes (it is *idempotent* in the strong sense of "no effect at all"). 3. **Movement test** — can I move the call earlier or later among other reads, or evaluate it inside a debugger watch window / assertion / log statement, without altering behaviour? A pure query: yes. An operation that fails any of these is a command. An operation that fails one of them **and** returns a meaningful value is a **CQS violation** — a "command-query mix". ### Why the separation pays off - **Safe to observe.** You can call queries from logging, assertions, `toString`, monitoring, and step-through debugging without the Heisenbug problem where inspecting a value changes it. - **Reasoning and refactoring.** Queries can be reordered, extracted into variables, inlined, or removed. Common-subexpression elimination, memoization, and lazy evaluation are all legal only for queries. - **Concurrency.** Pure queries need no exclusive lock and can run in parallel or against a replica; only commands need serialization. This is the direct ancestor of read/write-lock and read-replica strategies. - **Testing.** Queries are trivially testable (input → output, no fixtures to reset). Commands are tested by asserting on the state afterwards with a query — which only works if the query itself is side-effect-free. - **Retry safety.** In distributed systems you can freely retry a query when a response is lost; retrying a mixed operation may double-apply the effect. ### What CQS does **not** say - It does **not** forbid a command from *failing*. Throwing an exception, returning a success/failure result, or returning a validation-error object is normally considered compatible with CQS: that is reporting the outcome of the effect, not answering a separate question. Meyer's own style used exceptions and preconditions for this. - It does **not** forbid queries from consuming resources (CPU, time) or from touching *unobservable* internal state such as a memoization cache or a lazily-computed field. That distinction is often phrased as **observable** vs **benign/hidden** side effects. - It is a **method-level style rule inside a codebase**. It is *not* the same thing as **CQRS** (Command Query Responsibility Segregation), which is an architectural pattern about separating whole read and write *models*, often with separate stores. CQRS was named after CQS by Greg Young and generalizes it, but they live at different scales. ### Where it comes from and where it shows up The idea predates the name: it is the object-oriented restatement of the functional-programming distinction between **pure functions** (value in, value out) and **effects**. It appears again as `GET` vs `POST/PUT/DELETE` in HTTP (safe vs unsafe methods), as `SELECT` vs `INSERT/UPDATE/DELETE` in SQL, and as reads vs writes in almost every caching or replication design. CQS is the microscope-level version of a rule that keeps reappearing at every scale. ### Cost of the rule Separating a mixed operation into `peek()` + `remove()` costs one extra call, and in a concurrent setting that pair is no longer atomic — which is exactly why standard library APIs keep operations like `pop()` (return the top element *and* remove it) and compare-and-swap (write a value *and* return whether it succeeded). Meyer himself acknowledged those cases; CQS is a strong default with a small, deliberate set of exceptions, not a law.

  • Is a command allowed to return anything at all under CQS?
    Under the strict reading it returns nothing; the pragmatic reading allows it to report the outcome of its own effect — success/failure, an error result, or a generated identifier — because that is not answering an independent question about state. Returning a *separate* piece of domain state (e.g. the new full object graph) is where it starts to violate the spirit.
  • Does a method that only reads from a database, but writes an audit log row, count as a query?
    Strictly no — it changes observable state (the audit table), so it is a mixed operation. In practice most teams accept it as a benign/infrastructural effect, but it must be documented, because it breaks retry-safety and makes the read no longer free to repeat.

A light switch versus a light meter. The switch changes the room and tells you nothing; the meter tells you how bright the room is and changes nothing. A device that dims the lights every time you read the brightness would make the room impossible to reason about — that is a command-query mix.

context

open as a page

Name several widely accepted violations of Command-Query Separation in real APIs and explain why each exception is justified.

level: middleimportance: must knowfreq 46%

basics

~10 s

Common accepted mixes: stack pop(), queue poll(), iterator next(), compare-and-swap, atomic getAndIncrement, putIfAbsent, and getOrCreate. Each needs the read and write to happen as one atomic step, which two separate calls cannot guarantee.

open as a page

What is the difference between CQS (Command-Query Separation) and CQRS (Command Query Responsibility Segregation)?

level: seniorimportance: must knowfreq 62%

basics

~20 s

CQS is a method-level style rule: a method either mutates or returns, never both. CQRS is an architecture pattern: separate read and write models — often separate objects, services, or even databases — for the whole system or a bounded context.

open as a page

How does Command-Query Separation relate to referential transparency and pure functions, and what concrete capabilities does that unlock?

level: middleimportance: should knowfreq 34%

basics

~20 s

If queries have no side effects, a call can be replaced by its result without changing the program — that is referential transparency. It lets you cache, reorder, parallelize, retry, and safely inspect calls in logs and debuggers.

open as a page

How would you enforce Command-Query Separation across a large codebase and an HTTP API, and where does the principle break down?

level: principalimportance: should knowfreq 22%

basics

~20 s

Enforce it with conventions the tools can check: naming, return types (commands return nothing or just an outcome), separate command/query interfaces, and lint or architecture tests. On HTTP, map queries to safe methods like GET and commands to POST/PUT/DELETE.

open as a page

How do you distinguish an observable side effect from a benign one when deciding whether a method still counts as a query under CQS?

level: seniorimportance: nice to knowfreq 26%

basics

~20 s

A side effect is benign if no caller can tell it happened — like filling an internal cache. It is observable if any later call, another thread, or an external system can detect it. Only benign effects still count as a query.

open as a page