Design & Architecture

CQS — Command Query Separation

Intermediate

Command Query Separation is a simple, method-level rule: a method should either change state (a command, returning nothing) or return a value (a query, changing nothing) — never both. Asking a question should not alter the answer. It is small, cheap, and applies almost everywhere, and it is the principle that CQRS scales up to the architecture level.

CQS, coined by Bertrand Meyer, draws a line between two kinds of methods. A query computes and returns a result and has no observable side effects — you can call it twice and nothing changes. A command performs an action that changes state and returns nothing (or just an acknowledgement). The trouble comes from methods that do both: they return data and mutate, so reading becomes risky and reordering or removing a call has surprising effects.

CQS is a coding discipline, not an architecture — keep it distinct from CQRS, which applies the same idea at the system level with separate read and write models. CQS costs almost nothing to follow and makes code dramatically easier to reason about, so it is a near-universal default; it reinforces Separation of Concerns and Mutable vs Immutable Design.

Keep questions and actions apart

A query that mutates public Customer GetCustomer(Guid id) {
var c = repo.Load(id);
c.LastAccessedAt = DateTime.UtcNow; // hidden write
repo.Save(c);
return c;
}

"Get" implies a harmless read, but every call writes to the database. Logging or a debugger watch that calls it changes production state.

Split into query and command public Customer GetCustomer(Guid id) => repo.Load(id); // pure read

public Task RecordAccess(Guid id, DateTimeOffset at) // explicit write
=> repo.UpdateLastAccessed(id, at);

The read is safe to call freely; the write is an explicit, named action. Reordering or removing the read can no longer have side effects.

Why it pays off

Self-review checklist

Why it matters: When asking a question can change the answer, code becomes treacherous: a log line, a debugger watch, or a reordered call quietly alters state. CQS removes that whole class of surprise for almost no cost — queries are safe to call anywhere, commands are explicit about what they change — and it leaves code naturally shaped for CQRS if you ever need to scale reads and writes apart.