Design Principles Senior
How do you define 'simplicity' in software design, and why is simple code often harder to write than complex code?
Select the correct answer
It means using clever concise one-liners; simple code is hard because such tricks need advanced language features.
It means avoiding every abstraction layer; simple code is hard because concrete logic requires far more typing.
It means removing non-essential complexity; simple code is hard because clarity demands deep understanding and rework.
It means writing the fewest lines you can; simple code is hard because compact code always runs much slower.
Explain the difference between Dependency Inversion (the principle) and Dependency Injection (the pattern). Why should high-level modules depend on abstractions rather than low-level details?
Select the correct answer
DIP inverts control flow at runtime; DI is a compiler feature resolving concrete types automatically at build time.
DIP and DI are identical concepts, since injecting dependencies is the only way to invert module dependencies.
DIP is a principle where modules depend on abstractions; DI is a technique supplying dependencies externally.
DIP means avoiding all dependencies; DI is a way to inject code into classes to reduce the number of imports.
If a codebase is 'Rigid' and 'Fragile', which design principles are likely being violated?
Select the correct answer
Only Interface Segregation, because fat interfaces are the single sole cause of tightly coupled modules.
Liskov Substitution and Interface Segregation, since subclasses replace parents and clients share interfaces.
Only Dependency Injection, because failing to inject dependencies is the one thing that makes code fragile.
Open/Closed and Dependency Inversion, since tight coupling makes changes ripple out and break code.
Why is the Interface Segregation Principle important for compiled languages vs. interpreted languages?
Select the correct answer
In interpreted languages, fat interfaces cause runtime type errors that compiled languages catch at build time.
In interpreted languages, segregation reduces parsing time, which compiled languages avoid via binary caching.
In compiled languages, interfaces are optional, so segregation only matters when using dynamic interpreted code.
In compiled languages, changing a fat interface forces recompilation of all clients, even unaffected ones.
In what scenarios might strictly following SOLID principles lead to over-engineering or unnecessary complexity?
Select the correct answer
In large systems where multiple teams must coordinate on shared modules and contracts.
In small or short-lived projects where extra abstraction adds cost without a clear payoff.
In performance-critical code where interface calls are always slower than direct method calls.
Whenever a class exposes public methods that happen to be called by more than one client.
Explain the Liskov Substitution Principle without using the word 'inheritance'. Why is it a violation if a subtype throws an exception for a method defined in the base type?
Select the correct answer
A base type must expose only abstract methods so each subtype can define its own behavior.
A subtype must be usable wherever the base type is; an unexpected exception breaks that.
A subtype may narrow accepted inputs and widen outputs while keeping the same method names.
Two types are interchangeable whenever they simply share the same set of public methods.
What is behavioral subtyping? Can you explain why a 'Square' inheriting from a 'Rectangle' is often cited as a violation of the Liskov Substitution Principle?
Select the correct answer
Subtypes must honor supertype contracts; Square breaks the independent width and height of Rectangle.
Subtypes must reuse constructors; Square breaks because it accepts a single dimension unlike Rectangle.
Subtypes must add new methods; Square fails because it removes the area method that Rectangle defines.
Subtypes must share field names; Square differs by storing one side while Rectangle stores two sides.
When is tight coupling actually acceptable or even preferred over loose coupling?
Select the correct answer
When unit testing needs mocking, tight coupling makes isolating each component far easier to achieve.
When code must be reused across many unrelated modules that each evolve at different rates over time.
When systems are large and distributed, tight coupling helps teams scale their work independently.
When components are tightly related and always change together, extra abstraction only adds complexity.
What is the difference between 'Data Coupling' and 'Control Coupling', and which one is more dangerous for long-term maintainability?
Select the correct answer
Data coupling passes plain values while control coupling passes flags steering logic; control is worse.
Data coupling passes flags while control coupling passes records; control coupling is the safer of them.
Data coupling passes objects while control coupling shares memory addresses; both are equally harmful.
Data coupling shares global state while control coupling passes parameters; data coupling is far worse.
What is 'Temporal Coupling,' and why is it harder to detect than standard data coupling?
Select the correct answer
Objects are created and destroyed too frequently, which the garbage collector struggles to detect at runtime.
Data passed between modules changes type over time, so the compiler cannot flag the mismatch early enough.
Calls must happen in a set order, yet nothing in the method signatures reveals that hidden sequence.
Two modules share the same clock or timer, so timing bugs only appear under heavy production load spikes.
In design terms, what is the difference between 'rigidity' and 'fragility' in a codebase?
Select the correct answer
Rigidity means the code breaks easily; fragility means the code resists any attempt to change it at all.
Rigidity means poor performance under load; fragility means poor readability for new developers reading it.
Rigidity means too many dependencies exist; fragility means too few tests exist to catch regressions early.
Rigidity means changes are hard to make; fragility means changes break unexpected, unrelated parts.
When does coupling become cohesion, at what level of abstraction do related components transition from being 'coupled' to being part of a 'cohesive' module?
Select the correct answer
When components communicate over a network, the latency forces them to become a cohesive unit together.
When components are written by one team, ownership alone turns their coupling into genuine cohesion instead.
When related components serve one responsibility and sit together inside a single module boundary.
When unrelated components share a database, they naturally merge into one cohesive layer of the system.
What is 'Orthogonality' in software design, and how does it reduce the 'ripple effect' when a bug is fixed or a feature is added?
Select the correct answer
Components are layered strictly, so a change to one always flows downward through every layer and updates the lower modules automatically.
Components are duplicated for safety, so a change to one leaves the copies untouched and preserves the original behaviour in other parts.
Components share a single global state, so a change to one is instantly reflected everywhere and stays consistent across the whole system.
Components are independent, so a change to one is isolated and does not propagate unwanted effects into unrelated parts of the system.
Can you walk through the spectrum of cohesion types, from functional down to coincidental cohesion, and explain why some are worse than others?
Select the correct answer
Sequential cohesion (output feeds input) is best; procedural cohesion (steps share data) is worst since it mixes many unrelated data flows.
Coincidental cohesion (elements serve one task) is best; functional (elements grouped arbitrarily) is worst since it has no meaningful relation.
Functional cohesion (elements serve one task) is best; coincidental (elements grouped arbitrarily) is worst since it has no meaningful relation.
Temporal cohesion (elements run at one time) is best; logical cohesion (elements share one task) is worst since it forces a strict ordering.
What is 'content coupling' and 'common coupling', and why are they considered the most harmful forms of coupling?
Select the correct answer
Content coupling is passing whole records around; common coupling is passing single flags as arguments; both create verbose, wasteful links.
Content coupling is one module altering another's internals; common coupling is modules sharing global data; both create hidden, fragile links.
Content coupling is modules sharing global data; common coupling is a module altering another's internals; both create loose, harmless links.
Content coupling is one module calling another's methods; common coupling is modules sharing an interface; both create clean, explicit links.
Why are cyclic dependencies between modules considered harmful, and what is the principle of acyclic dependencies?
Select the correct answer
Cycles let modules share too much global state; the principle requires the dependency graph to route all calls through a central hub.
Cycles make modules run more slowly at startup; the principle requires the dependency graph to be flattened into one single module.
Cycles prevent modules from ever being reused alone; the principle requires the dependency graph to be limited to just three layers.
Cycles force modules to change and be tested together; the principle requires the dependency graph to form a directed acyclic graph.
What is 'connascence' as a way of reasoning about coupling, and how does it give a more nuanced view than simply 'tight vs loose'?
Select the correct answer
It classifies coupling by kind, strength, degree, and locality, so you can compare and rank dependencies instead of judging them binary.
It classifies coupling by runtime cost alone, so you can profile the slowest links in a system instead of reasoning about their structure.
It classifies coupling only by direction and count, so you can total the arrows in a diagram instead of judging their real strength at all.
It classifies coupling by the language used, so you can pick the safest framework for a module instead of measuring dependencies directly.
What is the difference between Encapsulation and Information Hiding, and why is hiding the reason for a design choice as important as hiding the data itself?
Select the correct answer
Encapsulation blocks all external access entirely while hiding merely renames fields so their meaning stays private
Encapsulation is a runtime security feature while hiding is only a compile-time restriction enforced by the language
Encapsulation exposes internal structure to subclasses while hiding prevents any inheritance of the concealed members
Encapsulation bundles data with behavior; hiding conceals decisions likely to change so clients don't depend on them
Explain the concept of 'Leaky Abstractions.' Why is it impossible to have a 'perfect' abstraction?
Select the correct answer
An abstraction leaks when underlying implementation details surface, because it cannot fully hide every real-world complexity
An abstraction leaks when memory is not released, because hidden resources gradually accumulate and eventually degrade performance
An abstraction leaks when it depends on another layer, because transitive dependencies always make a design impossible to test
An abstraction leaks when it exposes too few methods, because callers must then bypass it to finish any useful work
How do you decide the right level of abstraction for a component, and what happens when an abstraction is 'leaky'?
Select the correct answer
Pick the abstraction that requires the least code to write; a leak just indicates a performance bottleneck somewhere
Match abstraction to the client's needs and hide the rest; a leak forces clients to know the internals
Mirror the database schema as closely as you can; a leak means the underlying storage engine has been swapped out
Always choose the most generic abstraction possible; a leak simply means the component needs more public methods
Why is it often said that inheritance breaks encapsulation? In what scenarios is composition a safer choice for code reuse?
Select the correct answer
Inheritance is slower at runtime than composition, so composition should be preferred whenever memory usage and raw execution performance are top concerns.
Inheritance forces every subclass to be public, so composition should be used to keep helper classes private and fully hidden from all external callers.
Inheritance prevents polymorphism between types, so composition is required whenever different objects must share one common interface at runtime.
Subclasses rely on the base class's internal implementation, so base changes can silently break them; composition reuses behavior via a stable interface.
Explain the 'You Ain't Gonna Need It' (YAGNI) principle. How do you balance designing for future extensibility vs. avoiding speculative generality?
Select the correct answer
Avoid writing any abstractions at all until the product ships, then rewrite everything from scratch once the real requirements finally become known.
Build every plausible future feature upfront, since retrofitting later is costlier; extensibility should always be prioritized over short-term simplicity.
Build features only when actually required, not on speculation; add extensibility once there is concrete evidence of a genuine, current need.
Design the most generic solution possible early, because flexible frameworks reduce long-term risk more than solving today's narrow, immediate problem.
What is the 'Don't Repeat Yourself' (DRY) principle, and when can it be taken too far? What is 'wrong abstraction' in the context of DRY?
Select the correct answer
Each piece of knowledge has one authoritative source; over-applied, it couples unrelated code that only looks similar into a wrong abstraction.
Identical-looking code must always be merged immediately; a wrong abstraction is one that was extracted too late after the third or fourth repetition.
Every line of code must appear only once anywhere; a wrong abstraction is any duplicated helper method that two different modules happen to call at once.
All shared logic belongs in a base class; a wrong abstraction is when composition is used instead of inheritance to avoid repeating common behavior.
How do you determine if a solution is 'KISS' or just 'under-engineered'?
Select the correct answer
KISS means the code has the fewest lines possible; under-engineered means it uses more lines than a senior developer would have written for it.
KISS means the solution passes every test today; under-engineered means it will eventually need refactoring once new features are requested later.
KISS solves the actual requirements with the least complexity; under-engineered omits handling of real, known requirements or important edge cases.
KISS means avoiding all design patterns entirely; under-engineered means adding patterns before the team has agreed they are truly necessary here.
How do you distinguish between 'essential complexity' and 'accidental complexity' when reviewing a design?
Select the correct answer
Essential complexity is any logic in the core domain layer; accidental complexity is any logic that lives in the infrastructure or persistence layers.
Essential complexity is code that cannot be unit tested easily; accidental complexity is code that has full test coverage but is still hard to read.
Essential complexity is complexity the customer explicitly requested; accidental complexity is any behavior the developers added without a written ticket.
Essential complexity is inherent to the problem itself; accidental complexity comes from implementation choices, tooling, or design and can be removed.
How do you balance the KISS principle with the need to adhere to more complex principles like SOLID or Design Patterns?
Select the correct answer
Ignore KISS whenever a design pattern applies, since patterns are proven industry standards and following them always outweighs keeping code simple.
Treat KISS and SOLID as mutually exclusive, choosing KISS for prototypes and switching entirely to SOLID once the project reaches production scale.
Always apply every SOLID principle and known pattern upfront, because a fully decoupled design is inherently simpler for teams to maintain over time.
Apply patterns and SOLID only when they reduce real complexity or solve a present problem; otherwise the simpler solution better honors KISS.
What is the 'Principle of Least Commitment' (deferring decisions), and how does it help keep designs flexible?
Select the correct answer
Always pick the simplest algorithm first and never revisit it regardless of new requirements
Delay binding choices until needed, keeping options open so late changes stay cheap and easy
Assign the smallest possible responsibility to each class so coupling between them drops sharply
Commit to every design decision early so the whole team shares one fixed and stable plan
What is the GRASP 'Indirection' principle, and when does adding a layer of indirection help versus hurt a design?
Select the correct answer
Always add wrapper layers between objects since more indirection always lowers coupling everywhere
Insert a mediating object to reduce coupling; it hurts when the extra layer adds needless complexity
Assign responsibility to the class holding the most data, avoiding any mediator between components
Remove all intermediaries so objects talk directly, which keeps coupling low and code easy to trace
What are the GRASP principles (e.g., Information Expert, Pure Fabrication), and how do they supplement SOLID?
Select the correct answer
Rules that replace SOLID entirely by defining stricter constraints for every class in a large system
Patterns for assigning responsibilities to objects, guiding the fine-grained choices SOLID leaves open
Testing heuristics that verify SOLID compliance by measuring coupling metrics across all the modules
Deployment guidelines describing how responsibilities map to services, extending SOLID to the network
What is the GRASP 'Protected Variations' principle, and how does it relate to the Open/Closed Principle?
Select the correct answer
Freeze all released classes entirely so that no future variation to their code is ever permitted
Guard shared mutable state with locks so concurrent variations cannot corrupt the object's data
Split every class into two layers so that reads and writes vary entirely independently of each other
Wrap predicted points of instability behind a stable interface so change cannot ripple outward
What is the GRASP 'Controller' principle, and what problem does it solve in assigning responsibilities?
Select the correct answer
Assign every incoming request to the class with the most information about that particular event
Assign event handling to the widgets themselves so each screen fully manages its own business rules
Assign responsibilities to a single god object that centralizes and owns all application behaviour
Assign handling of system events to a use-case coordinator, keeping domain logic out of the UI
Explain the GRASP 'Creator' principle: how do you decide which class should be responsible for creating instances of another?
Select the correct answer
Let the class that most frequently calls the object's methods be the one responsible for creating it
Let whichever class defines the most abstract interface create the concrete implementing subclasses
Let a dedicated factory class create every object in the system to keep construction fully centralized
Let a class create instances it aggregates, contains, records, or has the data to initialize
What is the 'Fail-Fast' principle, how does it differ from 'Defensive Programming', and when should you use one over the other?
Select the correct answer
Fail-fast applies only to compiled languages, while defensive programming applies only to interpreted ones
Fail-fast logs the error and proceeds, while defensive code always throws an exception to the top caller
Fail-fast silently retries the operation, while defensive code halts the whole program on any invalid input
Fail-fast surfaces errors immediately at their source, while defensive code tolerates bad input and continues
Why is idempotence considered a vital design principle for distributed systems and API reliability?
Select the correct answer
Repeating the same request produces the same result, so retries after failures cannot cause duplicate effects
Repeating the same request rolls back prior writes, so the database is guaranteed to stay fully consistent
Repeating the same request runs faster each time, so caching layers can serve the response without new work
Repeating the same request is blocked outright, so the server rejects any duplicate call within a time window
Explain the concepts of Preconditions, Postconditions, and Invariants, and how they help create a 'contract' between a caller and a callee.
Select the correct answer
Preconditions and postconditions are both the caller's duty, while invariants are optional runtime checks the callee may skip in release builds.
Preconditions describe return values, postconditions describe input arguments, and invariants are the exceptions a method is permitted to throw on error.
Preconditions are the caller's duty to satisfy before a call, postconditions are the callee's guarantee afterward, and invariants stay true throughout.
Preconditions are the callee's guarantee before a call, postconditions are the caller's duty afterward, and invariants hold only at startup time.
Explain Postel's Law (The Robustness Principle). When should you be 'liberal in what you accept'?
Select the correct answer
Be strict about both input and output at all times, since accepting loose input always propagates corrupt data through the system.
Be tolerant of varied or imperfect input at system boundaries, while sending strictly well-formed, conservative output to others.
Be tolerant of varied output formats you emit, while requiring strictly well-formed input from every caller inside the system.
Be liberal about accepting any input everywhere so validation logic can be removed entirely, keeping both sides of a call simpler.
What are the trade-offs of Defensive Programming? When does it lead to 'cluttered' code that is harder to maintain?
Select the correct answer
Excessive checks on trusted internal calls bury business logic in guard clauses, adding noise without catching real faults.
Validating untrusted external input at the boundary buries the logic in guards and rarely prevents any genuine defect from occurring.
Removing all checks makes code shorter, but the guard clauses it eliminates were the only thing documenting a method's real contract.
Adding checks improves performance so much that the extra lines are always justified, even for calls entirely within a trusted module.
What is 'viscosity' as a design smell, and how does it discourage developers from doing the right thing?
Select the correct answer
When builds and tests run slowly, developers avoid running them, letting defects quietly accumulate over time.
When preserving the design is harder than hacking, developers take the easy shortcut, gradually eroding the design.
When code is duplicated widely, developers copy still more rather than extract, spreading the same logic further.
When modules depend on concretions, developers add more direct dependencies instead of introducing abstractions.
How do you maintain design principle adherence in a team with varying levels of seniority?
Select the correct answer
Let senior developers write all core modules while juniors handle only isolated bug fixes and simple tests.
Rely on automated linters alone so style stays uniform and no human review time is ever really needed.
Document the principles once in a wiki and assume everyone reads and applies them without any follow-up.
Establish shared standards and enforce them through code reviews, pairing, and mentoring rather than titles.
When two design principles conflict, how do you decide which one to prioritize? Can you give an example of a tension like DRY versus decoupling?
Select the correct answer
Weigh context and cost; forcing DRY can couple modules, so some duplication may be better to stay decoupled.
Always prefer DRY; eliminating duplication is the top priority and decoupling should always yield to reuse.
Always prefer decoupling; independence matters most, so duplication is fine and DRY is a minor concern.
Follow a fixed ranking of principles; the higher-ranked one always wins regardless of the specific situation.
When is it acceptable to deliberately break a design principle, and how do you justify that pragmatic decision?
Select the correct answer
Whenever it speeds up initial delivery; justify it by the deadline since principles just slow teams down anyway.
When the concrete cost of following it outweighs the benefit; justify it by documenting the deliberate tradeoff.
Only when a senior developer approves; justify it by their authority rather than by any measured tradeoff involved.
Never break principles in production; justify strict adherence because exceptions almost always cause defects later.
When should you choose 'Convention over Configuration' in a framework or library design?
Select the correct answer
When the framework supports many languages with incompatible configuration formats.
When performance is critical and defaults must be tuned manually for each case.
When sensible defaults cover most cases, reducing boilerplate while allowing overrides.
When every project needs unique settings that must be declared explicitly upfront.
What are the design trade-offs of 'Convention over Configuration'? When does a convention become a 'magic' hindrance to new developers?
Select the correct answer
It reduces file count but raises memory use because defaults load extra dependencies.
It cuts boilerplate but turns to magic when hidden behavior is hard to discover or debug.
It improves runtime speed but slows compilation when too many defaults are applied.
It enforces consistency but breaks type safety whenever conventions are overridden.