The Single Responsibility Principle says: a class should have only one reason to change. That’s it. One job. One focus. One responsibility.
In simple language, think of a chef who also does the restaurant’s accounting, manages the waitstaff, AND fixes the plumbing. Sure, one person could do all that, but if the tax laws change, should we be modifying the chef? Nope. Each role should be a separate person (class).
The Problem: A God Class
Here’s a class that does way too much:
# BAD -- this class has THREE reasons to change
class UserManager:
def __init__(self, db):
self.db = db
def create_user(self, name, email):
# user creation logic
user = {"name": name, "email": email}
self.db.save(user)
# email sending logic (reason to change #2)
self._send_welcome_email(email)
# logging logic (reason to change #3)
self._log(f"Created user: {name}")
def _send_welcome_email(self, email):
print(f"Sending welcome email to {email}")
# SMTP config, templates, retry logic...
def _log(self, message):
print(f"[LOG] {message}")
# File handling, log rotation, formatting...
// BAD -- this class has THREE reasons to change
class UserManager {
constructor(db) {
this.db = db;
}
createUser(name, email) {
// user creation logic
const user = { name, email };
this.db.save(user);
// email sending logic (reason to change #2)
this.sendWelcomeEmail(email);
// logging logic (reason to change #3)
this.log(`Created user: ${name}`);
}
sendWelcomeEmail(email) {
console.log(`Sending welcome email to ${email}`);
// SMTP config, templates, retry logic...
}
log(message) {
console.log(`[LOG] ${message}`);
// File handling, log rotation, formatting...
}
}
// BAD -- this class has THREE reasons to change
class UserManager {
private Database db;
UserManager(Database db) { this.db = db; }
void createUser(String name, String email) {
// user creation logic
User user = new User(name, email);
db.save(user);
// email sending logic (reason to change #2)
sendWelcomeEmail(email);
// logging logic (reason to change #3)
log("Created user: " + name);
}
private void sendWelcomeEmail(String email) {
System.out.println("Sending welcome email to " + email);
}
private void log(String message) {
System.out.println("[LOG] " + message);
}
}
Why is this bad? Because UserManager will change if:
- The user creation logic changes (new fields, validation rules)
- The email service changes (switch from SMTP to SendGrid)
- The logging format changes (switch to JSON logs)
Three reasons to change = three responsibilities = SRP violation.
The Fix: One Class, One Job
# GOOD -- each class has ONE responsibility
class EmailService:
def send_welcome_email(self, email):
print(f"Sending welcome email to {email}")
class Logger:
def log(self, message):
print(f"[LOG] {message}")
class UserService:
def __init__(self, db, email_service, logger):
self.db = db
self.email_service = email_service
self.logger = logger
def create_user(self, name, email):
user = {"name": name, "email": email}
self.db.save(user)
self.email_service.send_welcome_email(email)
self.logger.log(f"Created user: {name}")
// GOOD -- each class has ONE responsibility
class EmailService {
sendWelcomeEmail(email) {
console.log(`Sending welcome email to ${email}`);
}
}
class Logger {
log(message) {
console.log(`[LOG] ${message}`);
}
}
class UserService {
constructor(db, emailService, logger) {
this.db = db;
this.emailService = emailService;
this.logger = logger;
}
createUser(name, email) {
const user = { name, email };
this.db.save(user);
this.emailService.sendWelcomeEmail(email);
this.logger.log(`Created user: ${name}`);
}
}
// GOOD -- each class has ONE responsibility
class EmailService {
void sendWelcomeEmail(String email) {
System.out.println("Sending welcome email to " + email);
}
}
class Logger {
void log(String message) {
System.out.println("[LOG] " + message);
}
}
class UserService {
private Database db;
private EmailService emailService;
private Logger logger;
UserService(Database db, EmailService emailService, Logger logger) {
this.db = db;
this.emailService = emailService;
this.logger = logger;
}
void createUser(String name, String email) {
User user = new User(name, email);
db.save(user);
emailService.sendWelcomeEmail(email);
logger.log("Created user: " + name);
}
}
Now each class has exactly one reason to change:
UserService— only changes if user creation logic changesEmailService— only changes if email sending logic changesLogger— only changes if logging logic changes
How to Spot SRP Violations
Here are some red flags:
- The class name has “And” or “Manager” —
UserAndEmailManageris doing too much - The class has methods that don’t use each other — if
sendEmail()andgenerateReport()share nothing, they probably belong in different classes - We change the class for unrelated reasons — fixing a logging bug shouldn’t touch user creation code
- The class is growing and growing — if a file is 500+ lines, it’s probably doing too much
A Common Mistake
SRP doesn’t mean “a class should have only one method.” That’s too extreme. A UserService can have createUser(), updateUser(), deleteUser(), and findUser() — those are all part of the same responsibility: managing user data.
The key question is always: “How many reasons would this class change?” If the answer is more than one, split it up.
Why It Matters in Interviews
When we’re designing a Parking Lot system and we put payment logic, spot assignment, and vehicle tracking all in one ParkingLot class — that’s a red flag. The interviewer wants to see us naturally separate concerns into PaymentService, SpotManager, and VehicleTracker.
SRP is the foundation. Get this right, and the other SOLID principles become much easier.