Design Principles
How do you apply the Single Responsibility Principle (SRP) in large-scale
backend applications?
SRP states that a class should have only one reason to change — it should do one thing
only.
In large applications, this means separating concerns across services, modules, and
layers.
Examples:
A UserService should not handle email formatting or persistence logic.
Use dedicated components: UserService (business logic), UserRepository (data access),
EmailFormatter (presentation logic).
Benefits:
Improved maintainability and testability.
Reduces risk of unintended side effects when modifying logic.
How would you explain and enforce the Open/Closed Principle (OCP) in a
microservices environment?
OCP means software entities should be open for extension but closed for modification.
In microservices, extend behavior without modifying existing code through:
Feature toggles or strategy patterns to inject new behavior.
Event-driven architecture: emit and consume events to extend system behavior.
Example:
Adding a new notification type without changing NotificationService — use a pluggable
handler interface.
Enforcement techniques:
Define interfaces and abstract contracts.
Use DI frameworks (e.g., Spring) to inject extensions.
Design with modularity and plugin architecture in mind.
What is the Liskov Substitution Principle (LSP) and how can violating it lead to
subtle bugs?
LSP states that subclasses should be substitutable for their base classes without altering
program behavior.
Violations occur when a subclass overrides behavior in a way that breaks client
expectations.
Examples:
A subclass throws UnsupportedOperationException on a method that the parent
defines.
A Rectangle subclass that breaks area calculation because width and height setters are
overridden.
Problems caused by LSP violations:
Runtime errors or incorrect behavior in polymorphic code.
Breaks expectations of abstraction consumers — causes fragile code.
Prevention:
Favor composition over inheritance when behavior diverges.
Write contract tests for base class behavior and validate subclass conformance.
How does the Dependency Inversion Principle (DIP) improve testability and
flexibility in software design?
DIP states that high-level modules should not depend on low-level modules; both should
depend on abstractions.
Abstractions should not depend on details; details should depend on abstractions.
Benefits in practice:
Improved testability: easily mock abstractions during unit tests.
Flexible swapping of implementations without changing business logic.
Examples:
Controller depends on interface PaymentService, not on concrete StripePaymentService.
Repositories accessed via interface injected through DI container.
Common tools:
Dependency Injection frameworks (e.g., Spring).
Clean Architecture layering (use cases depending on ports/interfaces).
How would you balance the Interface Segregation Principle (ISP) with
practicality in real-world API design?
ISP encourages splitting large interfaces into smaller, more specific ones to avoid
forcing clients to depend on unused methods.
In practice:
Define fine-grained service interfaces (e.g., ReadOnlyUserService,
WriteOnlyUserService).
Avoid 'god interfaces' that try to do everything.
Trade-offs and balance:
Too many small interfaces can increase complexity.
Use cohesive groupings — group operations that are always used together.
Example:
Instead of IUserService with 20 methods, create IUserReader and IUserWriter
interfaces.