CQS Principle
CQS Principle
"Every method should either be a command that performs an action, or a query that returns data to the caller, but not both." — Bertrand Meyer, Object-Oriented Software Construction, 1988
Intent
CQS (Command Query Separation) is a method-level design discipline. Every method on an object belongs to one of two categories: a command that performs an action and returns void, or a query that returns data to the caller without producing any observable side effects. The discipline is enforced by naming convention and design intent, not by the runtime.
CQS is NOT an architectural pattern. It is a naming and design convention applied at the class level, within a single codebase or service. Developers who "do CQS" are separating method responsibilities inside their classes. This is a different concern from CQRS — which separates entire service models — and conflating the two is the primary pitfall. See the comparison table below.
The practical benefit of CQS is readability and testability: query methods can be called in any order without fear of altering state; command methods signal state change by returning void. Code reviewers and callers can reason about a method's contract from its signature alone.
When NOT to Use
- Semantically inseparable operations —
stack.pop()atomically reads the top element AND removes it; splitting intopeek()+remove()introduces a race condition in concurrent contexts. CQS is a principle, not a law — pragmatic violations are intentional and documented. - Performance-critical round-trip cases — if a read-after-write involves a remote call, the latency cost of strict CQS compliance may outweigh the clarity gain. Measure first.
- Built-in library types —
java.util.List.add()returnsbooleanand mutates state (a CQS violation). Conforming to library contracts takes precedence over CQS purity.
When to Use
- Any class where method clarity matters and future maintainers benefit from knowing whether a method mutates state.
- APIs where idempotent reads must be distinguishable from state-mutating writes (REST GET vs POST is a protocol-level echo of this principle).
- Code reviews: CQS makes violations visible — a method returning a value AND modifying state stands out immediately.
- Test setup: query methods can be safely called in assertions without risk of altering the state being tested.
How It Works
Commands — mutate state, return void (or Promise<void>). Naming convention: imperative verb — addItem, placeOrder, delete, reset.
Queries — return data, produce no observable side effects. Naming convention: noun or interrogative — getItems, findById, isValid, count.
The discipline is a convention enforced at design and review time. Nothing prevents a developer from writing a method that both mutates state and returns data; CQS says that method should be split into a command and a query. The constraint lives in the design conversation and code review, not in the compiler.
Class Diagram
classDiagram
class ShoppingCart {
-items : string[]
}
class Command {
<<interface>>
+addItem(item : string) void
+removeItem(item : string) void
+clear() void
}
class Query {
<<interface>>
+getItems() string[]
+getCount() int
+isEmpty() bool
}
ShoppingCart ..|> Command : implements
ShoppingCart ..|> Query : implements
note for Command "Mutates state\nReturns void\nImperative verb naming"
note for Query "Returns data\nNo side effects\nNoun/interrogative naming"
CQS vs CQRS
| Dimension | CQS | CQRS |
|---|---|---|
| Level | Method / class | Service / architecture |
| What separates | Method naming (void commands vs returning queries) | Write model from read model (separate services, stores) |
| Data stores | Same data store and model | Optionally separate data stores and schemas |
| Origin | Bertrand Meyer, 1988 | Greg Young, ~2010 (extending Fowler's CQS) |
| Purpose | Code clarity, testability | Read/write scalability, complex domain separation |
TypeScript Example
// CQS Principle — TypeScript (Bertrand Meyer, Object-Oriented Software Construction, 1988)
// Rule: a method either changes state (command) OR returns data (query) — never both
class ShoppingCart {
private items: string[] = [];
// Command: changes state, returns void
addItem(item: string): void {
this.items.push(item);
}
// Query: returns data, no side effects
getItems(): readonly string[] {
return this.items;
}
// Anti-pattern — violates CQS: changes state AND returns data
// popItem(): string { return this.items.pop()!; } // DON'T DO THIS
}Java Example
// CQS Principle — Java
// Command: mutates state, returns void
// Query: returns data, no mutation
class ShoppingCart {
private final List<String> items = new ArrayList<>();
// Command
public void addItem(String item) { items.add(item); }
// Query
public List<String> getItems() { return Collections.unmodifiableList(items); }
// Anti-pattern: boolean add(String item) — mutates AND returns data (java.util.List.add)
// CQS-compliant alternative: separate addItem (command) + contains (query)
}Lineage Backward
- Command-Pattern — GoF Command encapsulates a request as an object; CQS is the principle that commands should return void. Both share the command/query vocabulary; CQS is the design discipline that names the return-type constraint.
- Repository-Pattern — repository query methods (
findById,findAll) are CQS-compliant by design: they return data without mutating state.
Lineage Forward
- CQRS-Pattern — CQRS extends CQS from method level to service/architecture level, with separate command and query models that can scale independently and use different data stores.
Related Concepts
| Pattern | Relationship |
|---|---|
| Command-Pattern | GoF ancestor — encapsulates a request as an object; CQS names the void-return constraint on commands |
| Repository-Pattern | CQS-compliant queries — findById, findAll return data without side effects |
| CQRS-Pattern | Service-level generalisation — CQS at method level becomes CQRS at service/data-store level |
Sources
- Bertrand Meyer, Object-Oriented Software Construction, Prentice Hall, 1988 — CQS origin and definition
- Martin Fowler, CommandQuerySeparation — https://martinfowler.com/bliki/CommandQuerySeparation.html