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 operationsstack.pop() atomically reads the top element AND removes it; splitting into peek() + 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 typesjava.util.List.add() returns boolean and 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

DimensionCQSCQRS
LevelMethod / classService / architecture
What separatesMethod naming (void commands vs returning queries)Write model from read model (separate services, stores)
Data storesSame data store and modelOptionally separate data stores and schemas
OriginBertrand Meyer, 1988Greg Young, ~2010 (extending Fowler's CQS)
PurposeCode clarity, testabilityRead/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.
PatternRelationship
Command-PatternGoF ancestor — encapsulates a request as an object; CQS names the void-return constraint on commands
Repository-PatternCQS-compliant queries — findById, findAll return data without side effects
CQRS-PatternService-level generalisation — CQS at method level becomes CQRS at service/data-store level

Sources