← CodeClarityLab Home
Browse by Category
+ added · updated 7d
← Back to glossary

Command Query Separation (CQS)

quality Intermediate
debt(d7/e3/b5/t5)
d7 Detectability Operational debt — how invisible misuse is to your safety net

Closest to 'only careful code review or runtime testing' (d7). While phpstan and psalm are listed as detection tools, CQS violations are not automatically detectable — no static analyzer can reliably determine if a method 'should' be a query or command. The code patterns (method returning data AND writing to database, getter with side effects) require manual review to identify as violations rather than intentional design choices.

e3 Effort Remediation debt — work required to fix once spotted

Closest to 'simple parameterised fix' (e3). The quick_fix states splitting a function that does both query and command into two separate functions. This is a localized refactor — extracting one method into two, updating callers to use the appropriate method. Not a one-liner (e1), but contained within a single component's scope.

b5 Burden Structural debt — long-term weight of choosing wrong

Closest to 'persistent productivity tax' (b5). CQS applies across all contexts (web, cli, queue-worker) and is tagged as a principle affecting OOP and architecture. Once adopted, it shapes how every method signature is designed throughout the codebase. Not quite 'defines the system's shape' (b9), but more than a localized tax — it's a consistent mental model that affects all method design decisions.

t5 Trap Cognitive debt — how counter-intuitive correct behaviour is

Closest to 'notable trap' (t5). The misconception explicitly states developers confuse CQS (method-level rule) with CQRS (architectural pattern). This is a documented gotcha that most developers eventually learn, but initially causes confusion about scope and application. The common_mistakes also note acceptable exceptions (pop/shift) that can trip up developers applying the rule too rigidly.

About DEBT scoring →

Also Known As

CQS command query separation principle

TL;DR

Methods should either return a value (query) or change state (command), but never both.

Explanation

CQS (Bertrand Meyer) separates methods into commands (mutate state, return void) and queries (return value, no side effects). Mixing both in one method makes code harder to reason about — calling a method to get a value shouldn't change anything observable. Violations include pop() (removes and returns), which is sometimes justified for atomicity, but in most PHP code, separating read and write operations improves testability, predictability, and comprehension.

Common Misconception

CQS and CQRS are the same principle. CQS is a method-level rule (methods either return data or change state, never both). CQRS is an architectural pattern that applies this at the system level with separate read and write models.

Why It Matters

CQS makes methods predictable — a query always returns data without side effects, a command always changes state without returning data. Violating it creates hidden coupling and testing difficulty.

Common Mistakes

  • Methods that both modify state and return the new state — callers cannot query without triggering changes.
  • pop(), shift(), or similar stack/queue methods that violate CQS by design — acceptable exceptions but should be documented.
  • Repository save() methods that return the persisted entity — borderline; acceptable if the entity is enriched (ID assigned).
  • Not distinguishing CQS (method-level) from CQRS (architecture-level) — they operate at different scales.

Code Examples

✗ Vulnerable
// Violates CQS: modifies state AND returns a value
public function popItem(): Item {
    $item = array_shift($this->items); // mutation
    return $item;                      // query
}
✓ Fixed
// Separate command and query
public function removeFirst(): void  { array_shift($this->items); } // command
public function first(): Item        { return $this->items[0]; }    // query

// Or for genuine stacks/queues, accept the exception with clear naming:
public function dequeue(): Item { ... } // name signals it both reads and mutates

Added 15 Mar 2026
Edited 22 Mar 2026
Views 35
Rate this term
No ratings yet
🤖 AI Guestbook educational data only
| |
Last 30 days
0 pings T 0 pings F 0 pings S 3 pings S 1 ping M 2 pings T 0 pings W 2 pings T 0 pings F 0 pings S 1 ping S 0 pings M 0 pings T 0 pings W 0 pings T 0 pings F 1 ping S 3 pings S 0 pings M 0 pings T 0 pings W 0 pings T 0 pings F 0 pings S 0 pings S 3 pings M 0 pings T 0 pings W 0 pings T 0 pings F
No pings yet today
No pings yesterday
Amazonbot 10 Perplexity 8 Ahrefs 7 Google 2 ChatGPT 2
crawler 27 crawler_json 2
DEV INTEL Tools & Severity
🟡 Medium ⚙ Fix effort: Medium
⚡ Quick Fix
Functions that change state should return void (commands); functions that return data should not change state (queries) — if a function does both, split it
📦 Applies To
any web cli queue-worker
🔗 Prerequisites
🔍 Detection Hints
Method returning data AND writing to database; getter method with side effect of initialising something; query method that logs analytics event
Auto-detectable: ✗ No phpstan psalm
⚠ Related Problems
🤖 AI Agent
Confidence: Medium False Positives: High ✗ Manual fix Fix: High Context: Class Tests: Update

✓ schema.org compliant