Why the Single Responsibility Principle Protects Working Memory
In my previous article, Mystery Meat vs. Breadcrumb Systems, I argued that organizing code by feature reduces unnecessary exploration. Feature-based packages make it easier to navigate to the right place in the system.
But navigation alone doesn’t produce comprehension. You can open service.pricing immediately and still have no idea what you’re looking at. Finding the file only solves the location problem. Understanding it requires something else.
The Single Responsibility Principle is usually framed as a design rule: “a class should have one reason to change.” But that framing doesn’t explain why classes that follow it are often easier to understand.
The SRP protects working memory by limiting the number of responsibilities within a boundary. Usually the SRP applies to the boundary of a class, but the same constraint can be applied to packages and methods.
When a boundary contains one responsibility, understanding is easier. When it mixes responsibilities, reading turns into code simulation. Simulation is time consuming and adds cognitive overhead.
The Cost of Cognitive Thrash
Our working memory is limited. We can only hold a small number of conceptual frames in our heads at once.
By “conceptual frame,” I mean the mental model — the mode of reasoning — the reader uses to interpret what the code is doing. Validation logic is read differently than persistence logic. Calculation logic is read differently than logging.
A stable conceptual frame is one in which the reader can predict the type of reasoning required without repeatedly shifting mental models.
Each responsibility activates a conceptual frame. When a boundary contains multiple responsibilities, the reader must repeatedly switch frames. That context switching isn’t free. The reader has to set aside one mental model, load another, and preserve the relationships between them.
That repeated switching creates cognitive thrash.
Cognitive load theory separates intrinsic load (the complexity of the problem) from extraneous load (the cost of how it’s presented). The SRP doesn’t reduce intrinsic complexity; pricing rules are still pricing rules. But it reduces extraneous load by limiting unnecessary frame switching inside a boundary.
Code that follows the SRP reduces context switching by stabilizing the frame contained within each boundary.
SRP Across Every Boundary
The SRP can apply to more than just the boundary created by a class definition. Any structural boundary in the system can contain (or mix) conceptual frames.
At each level, the question is the same:
Does this boundary contain one stable conceptual frame, or does it mix several?
System Level: Packages
At the package level, we can group features together. Feature-based packages increase cohesion and enable navigation. They reduce the search space and make functionality visible in the structure of the system.
When packages are organized primarily by framework layer or persistence entity, features get fragmented. Instead of navigating to a visible feature boundary, readers must search and infer relationships across layers.
At the system level, this determines whether you can find functionality without relying on memory — yours, a teammate’s, or the original author’s (who left three years ago).
A cohesive package structure stabilizes feature-level frames; a fragmented structure forces reconstruction and search.
Class Level: Objects
At the class level, SRP is commonly defined as:
A class should only have one reason to change.
Cognitively, that means a class should represent one coherent responsibility which activates one stable conceptual frame.
A class that validates, calculates, coordinates, persists, and logs mixes frames. Readers can’t rely on the class name or structure to predict the pattern of logic inside. They must simulate execution, trace control flow, and hold multiple responsibilities in working memory.
When simulation replaces pattern recognition, reading slows down. Every interaction requires reconstruction.
Classes with a single responsibility contain one frame inside the boundary, so the reader knows what kind of logic to expect, as well as what does not belong there.
Good names reinforce that boundary. Class names with cohesive boundaries usually answer one or more of these questions:
- Purpose: Why does this class exist? (
InvoiceReconciliation*,OrderFulfillment*) - Role: What kind of logic is this? (
*Calculator,*Validator,*Orchestrator) - Constraint: What variant is this? (
*Cached*,*External*,*ReadOnly*) - Mechanism: How is it implemented? (
*Jdbc*,*Stripe*,*Rest*)
The role suffix often signals the one reason the class has to change:
OrderTotalCalculatorchanges if calculations change.CheckoutOrchestratorchanges if sequencing changes.RefundValidatorchanges if validation rules change.
These repeatable naming patterns enable pattern recognition. Pattern recognition works best when frames are stable.
Names don’t create cohesion, but they do constrain what belongs inside the boundary. If a name can’t meaningfully exclude logic, the class doesn’t represent a single stable frame.
This is why suffixes like *Service, *Helper or *Manager tend to become junk drawers. They describe broad roles, not stable conceptual frames. Almost anything can fit inside them — and once it does, frame containment erodes.
Method Level: Units of Thought
The SRP can apply at the method level too.
For most of my career, I used helper methods to prevent duplicate code or isolate especially complicated logic.
Recently, I began using helper methods as labels for units of thought.
Instead of writing a dense public method with comments preceding each section, I now extract helper methods as labeled steps:
validateRequest();
calculateTotals();
applyDiscounts();
persistOrder();
The public method becomes a narrative. Each call marks a structural boundary around one stable conceptual frame.
Instead of holding the entire implementation in working memory, the reader processes one frame at a time.
I don’t do this with every public method; just the ones that benefit from it.
A simple test:
Does the public method read like a clear sequence of steps after extraction?
If not, the extraction hasn’t improved frame containment. Keep the logic inline.
When Extraction Backfires
Extraction reduces density but increases indirection.
A boundary only helps if it contains a stable frame. When extraction forces the reader to jump across multiple files or through layers of trivial delegation, it replaces conceptual compression with navigational friction.
We want to increase cohesion, not decrease it.
Warning signs:
- Methods extracted that don’t represent a meaningful step.
- One-line delegations that merely rename trivial logic.
- Control flow that requires navigating through several classes to understand a simple process.
- “Micro-objects” that fragment one coherent idea across multiple files.
If extraction increases frame switching instead of reducing it, the boundary is no longer stabilizing the frame.
What Happens When Boundaries Degrade
SRP violations don’t just make a single class harder to read; over time, degraded boundaries affect the entire system.
Increased Mental Simulation
When a boundary contains multiple responsibilities, skimming stops being safe. Readers can’t rely on the name or the structure of the class to predict what belongs inside or what pattern of logic they’ll find. They must trace execution step-by-step, juggling multiple conceptual frames in working memory.
When frames become unstable, recognition becomes unreliable and readers fall back to simulation. Recognition is fast. Simulation is slow. Over time, slow becomes normal.
Decreased Code Review Efficacy
At a small scale, one unstable boundary is an annoyance. At a large scale, unstable boundaries create systemic fatigue.
Reviewers spend more time reconstructing mental models before they can even evaluate correctness. Frame instability forces deeper inspection on every change, and eventually reviewers adapt by conserving effort. They skim when it isn’t safe to skim.
Bugs hide in the noise because unstable boundaries made full understanding too expensive.
Cognitive thrash spreads beyond the original class and into the review process itself.
God Class Gravity
Classes with loosely named boundaries (OrderService, UserService) attract unrelated behavior. Because the name doesn’t constrain a single responsibility, almost anything fits. The class stops representing a stable conceptual frame and becomes an expanding collection of unrelated behaviors.
Developers hesitate to introduce new cohesive classes because doing so feels like violating the team’s conventions. Entropy wins.
Boundaries between functionality blur because the “entity service” contains all logic for all features that touch that entity.
Unit tests grow harder to write, read, and understand. They stop reinforcing a coherent frame and instead mirror the fragmentation of the production code.
Knowledge Concentration
When responsibility isn’t visible in structure, knowledge concentrates in people instead of boundaries.
Ownership narrows to the developer who wrote the feature. Others avoid touching or refactoring that code because it feels risky. The bus factor increases.
Knowledge centralizes, and unreadable structure increases risk concentration.
Systemic Entropy
As unstable boundaries multiply, feature boundaries degrade.
Packages become dumping grounds for everything in that architectural layer. Navigability decreases as the system grows. New functionality gets fragmented to fit the existing structure.
The cost compounds, and even experienced developers struggle to onboard or safely extend the system because it’s difficult to build a reliable mental model of where functionality lives.
Stable class-level boundaries reinforce package-level boundaries. When cohesion degrades at the class level, navigability degrades globally.
Systemic drag follows.
In a read-heavy discipline like software development, drag is lost performance. Readability now limits performance.
SRP Reinterpreted
SRP is often defined as:
A class should have one reason to change.
Cognitively, the rule could be defined as:
A boundary should contain one stable conceptual frame.
Software development is constrained by how quickly a team can find, understand, and safely change code. Cohesive boundaries protect comprehension at every level: package, class, and method.
The SRP reduces frame switching inside a boundary. That reduction compounds.
SRP aligns with constraints imposed by human cognition rather than being merely a stylistic preference. And individual cognition does not scale with system size.
When Write Velocity Outpaces Cognition
The number of boundaries grows as AI quickly generates code. The only way that growth remains sustainable is if those boundaries preserve frame stability instead of fragmenting it.
As code volume grows, developers spend more time reviewing and validating than writing. Effective code review relies heavily on pattern recognition. Pattern recognition depends on frame stability.
If boundaries between frames are unstable, AI amplifies the problem, and it spreads faster than ever.
When cohesion degrades, reading becomes simulation, simulation becomes cognitive thrash, thrash becomes fatigue, and fatigue becomes risk.
Applying the SRP across packages, classes and methods can help protect one resource that doesn’t scale: human cognition.
Next time, I’ll explore why AI makes readability more important, not less.