SOLID PrinciplesSingle Responsibility PrincipleHard⏱️ ~3 min

SRP Trade-offs: When to Apply and When to Defer

The Balancing Act: SRP vs Pragmatism

SRP is not an absolute law. Applying it blindly can lead to over-engineering with dozens of tiny classes. The key is understanding when SRP adds value and when it creates unnecessary complexity. The decision depends on volatility, team structure, and system maturity.

When SRP is Essential

First, when responsibilities serve different actors or departments. If the Accounting team might change fee calculation while the Operations team changes spot allocation, separate them. Cross-team changes to the same class create merge conflicts and coordination overhead.

Second, when responsibilities change at different rates. If pricing rules change weekly but spot allocation algorithms change annually, isolating FeeCalculator from SpotManager prevents constant retesting of stable code.

Third, when responsibilities have different reuse contexts. If FeeCalculator might be reused in a valet service or monthly membership system, extracting it enables reuse without carrying parking spot logic.

Fourth, when testing requires different setups. If testing fee calculation requires mock pricing databases while testing spot allocation requires mock vehicle queues, separation simplifies test setup.

When SRP is Overkill

First, in early prototypes or MVPs (Minimum Viable Products). If you are validating a business idea, a single ParkingManager class that handles spots and fees is acceptable. Premature separation adds complexity before requirements stabilize. However, once the product proves viable, refactor toward SRP before technical debt accumulates.

Second, when responsibilities are truly coupled in the domain. Consider a Transaction class in a banking system. It might validate, execute, and log operations. These seem like separate responsibilities, but in banking, they must happen atomically. Separating them could violate domain integrity. In this case, keep them together but document why.

Third, for simple CRUD (Create, Read, Update, Delete) entities. A CustomerRepository that only saves and retrieves customer data does not need separate reader and writer classes unless your system uses CQRS (Command Query Responsibility Segregation). If load characteristics differ (many reads, few writes), then separation makes sense. Otherwise, keep it simple.

Fourth, when team size is tiny (one or two developers). If one person owns the entire parking module, the organizational benefit of SRP (isolating different actors) is minimal. Focus on other principles like readability and testability first.

SRP vs Single Method Classes

A common anti-pattern is creating classes with only one method to "follow SRP." This confuses SRP with the Single Method pattern. SRP allows multiple methods as long as they serve the same responsibility. A FeeCalculator can have calculateHourlyFee(), calculateDailyFee(), and applyDiscount() because they all serve the fee calculation responsibility.

Interview Tip: If an interviewer says "Isn't this over-engineering?", acknowledge the trade-off. Say: "For an MVP, I would start with fewer classes. But for a production system with multiple teams, SRP prevents merge conflicts and allows independent deployment of pricing changes."

Comparison with Related Approaches

ApproachWhen to UseWhen NOT to Use
SRP (Separate Classes)Multiple actors, different change rates, production systemsMVPs, small teams, truly coupled domain logic
God Class (All in One)Quick prototypes, throwaway code, single-person projectsMulti-team projects, long-lived systems
Module-Level SeparationMicroservices, when responsibilities warrant separate deploymentsMonoliths, when network overhead outweighs benefits

Red Flags of Over-Application

If you have classes named DataHolder, DataProcessor, and DataValidator with no clear business meaning, you have likely over-applied SRP. Class names should reflect domain concepts, not technical operations. Instead, use Invoice, FeeCalculator, and TicketValidator which map to business responsibilities.

Another red flag is excessive indirection. If tracing a feature requires jumping through six classes with no clear responsibility boundaries, simplify. SRP should clarify structure, not obscure it.

Refactoring Timeline

Start with coarser boundaries and refine as pain points emerge. If changing pricing logic breaks spot allocation tests, that is a signal to separate FeeCalculator. If the Accounting team requests fee changes weekly while spot logic stays stable, that is a signal. Let real change patterns guide refactoring, not hypothetical future needs.

💡 Key Takeaways
Apply SRP when responsibilities serve different actors or change at different rates
Defer SRP in MVPs, small teams, or when domain logic is truly coupled
SRP allows multiple methods per class as long as they serve one responsibility
Over-application leads to excessive indirection and anemic domain models
📌 Examples
1Essential: Separating FeeCalculator (Finance team) from SpotManager (Operations team)
2Overkill: Separating CustomerRepository into CustomerReader and CustomerWriter in a simple CRUD app
← Back to Single Responsibility Principle Overview
SRP Trade-offs: When to Apply and When to Defer | Single Responsibility Principle - System Overflow