How do the five SOLID principles reinforce one another? Give a concrete chain showing how violating one causes violations of the others.
answer
- SRP -> ISP -> LSP -> OCP, DIP is the mechanism
- fat interface -> stub throws -> instanceof -> caller edits
- chase the root violation, not each symptom
- tension: indirection vs readability, DIP everywhere = ceremony
- underneath: cohesion, coupling, information hiding
basics
~20 sThey are not independent. A fat interface (ISP violation) forces implementations to stub methods they cannot support (LSP violation), so callers add type checks, which means adding a type forces editing callers (OCP violation) - and callers now depend on concrete types (DIP violation).
solid answer
~50 sSOLID is one idea - manage change by aligning code with its axes of variation - expressed five ways. The dependencies run roughly: SRP gives a module a single change axis; ISP keeps the interfaces that expose it small and role-specific; LSP guarantees every implementation of such an interface really is interchangeable; OCP is the payoff - because implementations are interchangeable, new behaviour arrives as new implementations rather than edits; DIP is the mechanism - callers depend on the abstraction, not the concretion, which is what makes substitution possible at wiring time. Read backwards, the failure chain is just as tight: a fat interface makes some implementations stub methods (LSP breaks), callers defend with instanceof checks (OCP breaks), and those checks name concrete types (DIP breaks). Conversely, a class with many reasons to change (SRP) tends to grow a fat interface, restarting the chain. The unifying measures underneath are cohesion and coupling.
go deeper
Give one concrete chain - a fat interface forces stub methods, which forces callers to check types - and note that the principles support each other.
Name each principle correctly and describe both the forward chain (ISP enables LSP enables OCP, DIP supplies the mechanism) and the fat-Device failure example.
Identify the root-cause pattern (fix the interface, symptoms disappear), articulate the tensions (indirection, YAGNI, DIP ceremony), and root the whole thing in cohesion/coupling.
Position SOLID as heuristics under information hiding and modularity, map them to module/service boundaries and to non-OO paradigms, and describe how you would prioritise which to enforce in a given codebase given its actual change history.
## Why interplay matters SOLID is often taught as five unrelated rules; in practice they are five views of one goal: **make change cheap by putting the seams where change happens**. Interviewers ask about interplay to see whether you understand the mechanism or just memorised acronyms. Quick definitions used below: - **SRP** - one reason to change per module (one actor). - **OCP** - add behaviour by adding code, not by editing existing code. - **LSP** - any implementation is usable where the abstraction is expected. - **ISP** - clients should not be forced to depend on methods they do not use; prefer small role interfaces. - **DIP** - high-level policy and low-level detail both depend on abstractions; the abstraction is owned by the policy side, so the dependency arrow points away from details. ## The forward chain (how they build on each other) 1. **SRP -> ISP.** A module with one reason to change naturally exposes a small, coherent interface. Multiple responsibilities produce a wide interface serving multiple clients. 2. **ISP -> LSP.** A narrow interface only asks for what implementations can honestly deliver, so nobody needs a stub or a 'not supported' throw. Fat interfaces manufacture LSP violations. 3. **LSP -> OCP.** OCP only works if callers can accept any implementation blindly. The moment one implementation is not substitutable, callers add special cases, and the caller is no longer closed against new implementations. 4. **DIP -> OCP.** Depending on an abstraction rather than a concrete class is the *mechanism* by which a caller stays unmodified while implementations are swapped or added. Without inversion there is nothing to extend. 5. **OCP -> SRP (feedback).** Extending by adding implementations keeps existing modules single-purpose instead of accreting branches. ## The failure chain (one violation cascading) A concrete example - a `Device` interface with `read()`, `write()`, `seek()`, `flash()`: - **ISP violated**: a read-only sensor must implement `write`, `seek`, `flash`. - **LSP violated**: it implements them by throwing `UnsupportedOperationException`. - **OCP violated**: callers defend with `if (device.supportsWrite())` or `if (d instanceof Sensor)`; adding a new device kind means editing every such caller. - **DIP violated**: those checks name concrete types, so high-level policy now depends on low-level details; the caller cannot be unit-tested without the real types. - **SRP violated**: the caller now owns device-capability knowledge in addition to its own job, giving it a second reason to change. One bad interface produced all five. Fixing the root (split into `ReadableDevice`, `WritableDevice`, `SeekableDevice`) dissolves the rest - which is the practical lesson: chase the root violation, not each symptom. ## Tensions between them (they are not always aligned) - **SRP vs. OCP/ISP indirection.** Aggressive splitting produces many small types; each is single-purpose but the *system* becomes harder to read. Cohesion at system level can suffer while cohesion at class level improves. - **LSP vs. reuse-by-inheritance.** Inheritance is an easy way to reuse code, but honouring LSP often forbids the most convenient hierarchy, pushing you to composition and more wiring code. - **DIP vs. simplicity.** Inverting every dependency (including stable ones like a date library or a standard collection) adds interfaces with a single implementation forever - pure ceremony. Invert across *volatile* or *policy* boundaries, not everywhere. - **OCP vs. YAGNI.** Closing against a change that never comes is dead weight; closing against the wrong axis is worse, because the abstraction fights the real change when it arrives. ## The deeper layer All five are heuristics for **high cohesion and low coupling**, and for **information hiding** (Parnas: modularise around what is likely to change, and hide those decisions behind interfaces). When SOLID advice and those fundamentals disagree, the fundamentals win - SOLID is the teaching device, not the goal. In non-OO paradigms the same ideas appear differently: higher-order functions provide OCP, structural typing provides ISP, and passing behaviour as arguments provides DIP.
- If you could only enforce two of the five principles on a legacy codebase, which would you pick and why?Typically SRP and DIP at the boundaries. SRP keeps change localised, which is what makes any further refactoring feasible, and DIP at the volatile boundaries (database, external services, clock, randomness) makes the code testable, which is the prerequisite for changing it safely. OCP and ISP follow naturally once those two hold; LSP mostly stops being an issue when inheritance is replaced by composition.
- Where do the principles pull against each other?SRP and ISP push toward many small types; readability and navigability push back, because system-level cohesion drops when a feature is spread over fifteen files. DIP applied uniformly produces interfaces with one implementation forever. OCP conflicts with YAGNI whenever the axis of variation is a guess. Resolve by applying them where change has already been observed rather than uniformly.
- How do these ideas appear outside object-oriented code?OCP appears as higher-order functions, plugin registries and typeclass/trait-based dispatch; ISP as structural typing or passing exactly the functions needed instead of a whole record; DIP as passing behaviour (a function or capability) into a pure core; LSP as parametricity and honouring type-class laws; SRP as small composable functions and pure cores with effects at the edge.
They work like the parts of a plug standard: agree on a small contract (ISP), guarantee every certified device honours it (LSP), and any new device works in any socket without rewiring (OCP) because the socket was built against the standard, not against a specific appliance (DIP).
saying these in an interview costs you the question
- Treating the five principles as independent checklist items with no causal relationships.
- Claiming SOLID is universally applicable with no tensions or costs.
- Saying DIP just means 'use a dependency-injection framework' - the principle is about who owns the abstraction, not about a container.
- Confusing ISP with SRP: ISP is about what *clients* are forced to depend on; SRP is about a module's reasons to change.
- Asserting that following all five always yields simpler code, ignoring the indirection cost.