INTERVIEW_QUESTIONS
SOLID Principles Interview Questions for Senior Engineers (2026)
Master SOLID principles interview questions with real-world examples covering Single Responsibility, Open/Closed, Liskov Substitution, Interface Segregation, and Dependency Inversion for senior roles.
Why SOLID Principles Matter in Senior Engineering Interviews
SOLID principles are the foundation of maintainable, extensible, and testable object-oriented design. At the senior level, interviewers use SOLID questions to assess whether you can design systems that evolve gracefully as requirements change, or whether your code becomes increasingly rigid and fragile over time.
The difference between junior and senior responses is stark. Juniors recite definitions. Seniors discuss trade-offs, show real-world examples where following a principle helped (and where over-applying it hurt), and connect principles to broader architectural decisions. Companies like Google, Amazon, and Stripe test SOLID understanding because it predicts how well an engineer designs maintainable systems.
This guide covers SOLID principles with deep, interview-ready answers. For related topics, see our design patterns guide, backend development crash course, and system design interview guide.
1. Explain the Single Responsibility Principle (SRP) with a real-world example.
What the interviewer is really asking: Can you identify when a class or module is doing too much, and can you decompose it effectively?
Answer framework:
SRP states that a class should have only one reason to change — one responsibility, one axis of change. Robert C. Martin defines it as "a class should have only one actor (stakeholder) it serves."
Bad example: a UserService that handles user registration, password hashing, email sending, and audit logging. When the email template changes, the UserService changes. When the password policy changes, the UserService changes. When the audit format changes, the UserService changes. Three different actors (marketing, security, compliance) drive changes to the same class.
Refactored:
UserRegistrationService: Orchestrates the registration flowPasswordHasher: Handles password hashing and validationEmailNotifier: Sends emails using templatesAuditLogger: Records audit events
Each class has one reason to change and one actor it serves.
Nuances:
- SRP does not mean a class should have only one method. It means one cohesive responsibility. A
UserRepositorywithfindById,findByEmail,save, anddeletehas one responsibility — data access for users. - Over-applying SRP creates too many tiny classes that are harder to navigate than a moderately sized, cohesive class. Balance SRP with practical readability.
- At the module level, SRP applies to microservices: each service should own one business domain. A service handling both order management and inventory management will change for two different business reasons.
See our design patterns guide and backend development crash course.
Follow-up questions:
- How do you decide whether two pieces of functionality are "one responsibility" or "two"?
- How does SRP apply to microservice boundaries?
- Can you have too much SRP? What are the signs?
2. How does the Open/Closed Principle (OCP) guide software design?
What the interviewer is really asking: Can you design code that's extensible without modification? This is the principle most directly connected to design patterns.
Answer framework:
OCP states that software entities should be open for extension but closed for modification. You should be able to add new behavior without changing existing, tested code.
Practical example: a payment processing system initially supports credit cards. When PayPal is added, a naive approach modifies the existing PaymentProcessor class with a new if branch. Every new payment method modifies the same class, risking regression and requiring retesting.
OCP-compliant approach: define a PaymentMethod interface with a process(amount) method. CreditCardPayment, PayPalPayment, and future methods implement this interface. The PaymentProcessor delegates to the interface without knowing the specific implementation. Adding a new payment method creates a new class — no existing code is modified.
How to achieve OCP:
- Strategy pattern: Encapsulate varying algorithms behind an interface
- Plugin architecture: New functionality is added as plugins that register with the system
- Event-driven design: Existing code emits events; new behavior subscribes to events without modifying the emitter
- Configuration over code: Behavior changes through configuration (feature flags, rule engines) rather than code changes
Nuances:
- OCP doesn't mean you never modify existing code. It means your design anticipates the most likely axes of change and makes extension easy along those axes.
- You can't predict all future changes. Apply OCP where you see a pattern of change (new types being added frequently) rather than everywhere.
- In dynamic languages (Python, JavaScript), OCP is achieved through duck typing and first-class functions rather than formal interfaces.
See our design patterns guide and system design interview guide.
Follow-up questions:
- How do feature flags relate to the Open/Closed Principle?
- When does following OCP add unnecessary complexity?
- How does OCP apply to REST API design (API versioning)?
3. Explain the Liskov Substitution Principle (LSP) and common violations.
What the interviewer is really asking: Do you understand behavioral subtyping, not just structural inheritance? LSP violations are a common source of subtle bugs.
Answer framework:
LSP states that objects of a supertype should be replaceable with objects of a subtype without altering the correctness of the program. A subclass must be usable anywhere its parent class is used, without the client needing to know the difference.
Classic violation — the Square/Rectangle problem: Square extends Rectangle. A Rectangle allows independent width and height changes. A Square overrides setWidth() to also set height (and vice versa). Code that expects a Rectangle and sets width=4, height=5 gets area=20. With a Square, setting width=4 then height=5 gives area=25 (because setting height also changed width). The subtype breaks the supertype's contract.
Real-world violations:
- A
ReadOnlyListextendingListthat throwsUnsupportedOperationExceptiononadd(). Code expecting aListcallsadd()and crashes. - A
CachedRepositoryextendingRepositorywherefindAll()returns stale data. Code expecting fresh data fromRepositorygets incorrect results. - An
AdminUserextendingUserwheregetPermissions()returns all permissions regardless of role assignment. Code relying on role-based permissions breaks.
How to comply with LSP:
- Preconditions cannot be strengthened: A subtype cannot require more from callers than the supertype.
- Postconditions cannot be weakened: A subtype must guarantee at least what the supertype guarantees.
- Invariants must be preserved: Properties that are always true for the supertype must remain true for the subtype.
Practical guidance: prefer composition over inheritance. Instead of Square extends Rectangle, create a Shape interface with area() and perimeter() methods, implemented independently by Square and Rectangle. This avoids the inheritance hierarchy that creates LSP violations.
See our design patterns guide and backend development crash course.
Follow-up questions:
- How does LSP relate to the concept of "design by contract"?
- What is behavioral subtyping, and how does it differ from structural subtyping?
- How do you test for LSP violations?
4. How does the Interface Segregation Principle (ISP) improve API design?
What the interviewer is really asking: Can you design focused interfaces that don't force implementers to depend on methods they don't use?
Answer framework:
ISP states that clients should not be forced to depend on interfaces they don't use. Many specific interfaces are better than one general-purpose interface.
Violation example: a Worker interface with work(), eat(), sleep(), and takeVacation(). A RobotWorker implementing this interface must provide empty implementations for eat(), sleep(), and takeVacation(). The interface forces the robot to depend on methods it can't use.
Refactored: Workable (with work()), Feedable (with eat()), Restable (with sleep()). HumanWorker implements all three. RobotWorker implements only Workable.
Real-world application — repository interfaces:
Bad: Repository<T> with find(), save(), update(), delete(), bulkInsert(), transaction(), rawQuery(). A read-only service forced to depend on write methods.
Good: ReadableRepository<T> (with find(), findAll(), findBy()), WritableRepository<T> (with save(), update(), delete()). Services depend only on the interfaces they use. A reporting service depends on ReadableRepository — it cannot accidentally write data.
ISP in Go: Go's interfaces are implicitly implemented (no implements keyword). This naturally encourages small, focused interfaces. The standard library exemplifies this: io.Reader (just Read()), io.Writer (just Write()), io.Closer (just Close()). A function that only needs to read accepts io.Reader, not io.ReadWriteCloser.
ISP in APIs: apply ISP to REST API design. Instead of one endpoint that returns all user data (GET /users/{id} returning 50 fields), provide focused endpoints (GET /users/{id}/profile, GET /users/{id}/preferences, GET /users/{id}/billing). Clients fetch only the data they need.
See our backend development crash course and system design interview guide.
Follow-up questions:
- How does ISP relate to the concept of role interfaces?
- How do you balance ISP with having too many tiny interfaces?
- How does Go's implicit interface implementation embody ISP?
5. Explain the Dependency Inversion Principle (DIP) and dependency injection.
What the interviewer is really asking: Do you understand the most architecturally impactful SOLID principle? DIP shapes the structure of entire systems.
Answer framework:
DIP states two things:
- 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.
Without DIP: OrderService directly creates and uses PostgresOrderRepository. The high-level business logic depends on the low-level database access implementation. Changing the database requires changing the business logic.
With DIP: OrderService depends on the OrderRepository interface (abstraction). PostgresOrderRepository implements the interface (detail depends on abstraction). The dependency direction is inverted — the concrete implementation depends on the abstraction defined in the business logic layer.
Dependency Injection is the mechanism for achieving DIP. Instead of a class creating its dependencies, they're provided (injected) from outside:
- Constructor injection: Dependencies passed through the constructor. Most common and preferred — makes dependencies explicit, enables immutability.
- Method injection: Dependencies passed as method parameters. Used when the dependency varies per call.
- Property/setter injection: Dependencies set through setters. Allows optional dependencies but creates temporal coupling (object may be in an invalid state between construction and injection).
DI frameworks (Spring, Guice, tsyringe) automate dependency wiring. But manual DI (just passing dependencies through constructors) is perfectly valid and often clearer for smaller codebases.
Architectural impact: DIP enables the Ports and Adapters (Hexagonal) architecture. The core domain logic defines interfaces (ports). Infrastructure components (database adapters, API clients, message queue consumers) implement those interfaces. The domain has zero dependencies on infrastructure — it can be tested with in-memory implementations.
See our design patterns guide and backend development crash course.
Follow-up questions:
- What is the difference between DIP, DI, and IoC (Inversion of Control)?
- How does DIP apply to microservice architecture?
- When does DIP add unnecessary complexity?
6. How do SOLID principles apply differently in functional programming?
What the interviewer is really asking: Do you understand that SOLID was designed for OOP but its concepts translate to other paradigms?
Answer framework:
SOLID principles originated in object-oriented design, but their underlying goals (cohesion, decoupling, extensibility, testability) apply to functional programming with different mechanisms.
SRP → Function purity and composition: In FP, a function does one thing. Pure functions (no side effects, deterministic output) naturally have a single responsibility. Complex behavior is built by composing simple functions: pipe(validate, transform, persist).
OCP → Higher-order functions: Functions are open for extension through composition and higher-order functions. Instead of extending a class, pass a function parameter: processPayment(order, validateFn, chargeFn). Adding new behavior means passing a different function — the processPayment function itself doesn't change.
LSP → Type signatures and contracts: In strongly typed FP (Haskell, Scala, Rust), type signatures serve as contracts. A function accepting Functor[A] works with any functor implementation. The type system enforces substitutability.
ISP → Small function signatures: Functions that take fewer parameters naturally follow ISP. A function that needs only a reader doesn't accept a full database connection. In TypeScript: function readData(reader: { read: () => string }) rather than function readData(db: FullDatabaseConnection).
DIP → First-class functions as dependencies: Instead of injecting interface implementations, pass functions. A service that needs to send notifications accepts sendFn: (msg: string) => void rather than depending on a NotificationService class. Testing is trivial — pass a mock function.
The key insight: SOLID principles describe goals (modularity, flexibility, testability). OOP achieves them through interfaces, inheritance, and polymorphism. FP achieves them through function composition, higher-order functions, and type systems. The goals are the same; the mechanisms differ.
See our backend development crash course and system design interview guide.
Follow-up questions:
- How does immutability in FP naturally support some SOLID principles?
- What are the equivalents of design patterns in functional programming?
- How does Rust's trait system embody SOLID principles?
7. How do you apply SOLID principles to microservice design?
What the interviewer is really asking: Can you think about SOLID at the architecture level, not just the code level?
Answer framework:
SOLID principles scale from individual classes to service-level architecture:
SRP for services: Each microservice owns one bounded context — one business domain. The Order Service handles order lifecycle. The Inventory Service handles stock management. If a service changes for multiple business reasons (both order processing and payment logic), it should be split.
OCP for services: Services should be extendable through events and APIs without modifying existing services. When a new notification channel is needed, create a new notification service that subscribes to existing domain events — don't modify the Order Service to send Slack notifications.
LSP for services: Service instances behind a load balancer must be interchangeable. A new version deployed to some instances must maintain the same API contract. Breaking API changes require versioning, not silent behavioral changes.
ISP for APIs: Don't create monolithic APIs. A UserService shouldn't expose internal admin operations through the same API as user-facing operations. Use separate API surfaces: public API (user-facing), internal API (service-to-service), and admin API (operational). Each consumer depends only on the API surface it needs.
DIP for services: Services depend on abstractions (API contracts, message schemas) not implementations. Service A doesn't know that Service B uses PostgreSQL. They communicate through defined interfaces (REST contracts, Protobuf schemas, event schemas). If Service B switches to MongoDB, Service A doesn't change.
See our microservices concepts, system design interview guide, and distributed systems guide.
Follow-up questions:
- How does domain-driven design (DDD) relate to SOLID principles in microservices?
- When does splitting a service for SRP create more problems than it solves?
- How do you handle cross-cutting concerns (logging, tracing) across services without violating SRP?
8. What code smells indicate SOLID principle violations?
What the interviewer is really asking: Can you identify design problems in existing code and prescribe the right SOLID principle to fix them?
Answer framework:
SRP violations:
- God class: A class with 20+ methods covering multiple concerns (data access, business logic, formatting, notification)
- Shotgun surgery: A single change requires modifying many classes (the responsibility is scattered)
- Divergent change: A class changes for many different reasons
OCP violations:
- Growing switch/if-else on type: Every new type requires modifying existing code
- Modifying tested code to add features: Risk of regression with each feature addition
- Feature envy: Methods that primarily use another class's data indicate wrong placement
LSP violations:
- Throwing UnsupportedOperationException in a subclass method
- Overriding methods to do nothing (empty implementation)
- Instanceof/type-checking in client code: If clients need to check the subtype, the substitution is broken
ISP violations:
- Fat interfaces: Interfaces with 10+ methods where most implementers only use a few
- Adapter hell: Many adapter classes that provide empty implementations for unused interface methods
- Clients importing modules they don't use
DIP violations:
- Direct instantiation of dependencies:
new PostgresRepo()inside business logic - Import of infrastructure packages in domain logic
- Difficulty writing unit tests without a database/API running
See our design patterns guide and career path to staff engineer.
Follow-up questions:
- How do you prioritize which code smells to fix first?
- What is the relationship between code smells and technical debt?
- How do you introduce SOLID principles to a team that doesn't follow them?
9. How do you balance SOLID principles with pragmatism?
What the interviewer is really asking: This is the real senior question. Can you apply principles judiciously rather than dogmatically?
Answer framework:
SOLID principles are guidelines, not laws. Over-applying them creates over-engineered systems that are harder to understand than the problem they solve.
Signs of over-application:
- Too many abstractions: An interface with a single implementation and no realistic second implementation. Adding an interface between every class adds indirection without benefit.
- Tiny classes everywhere: 200 classes in a module where 30 well-structured ones would be clearer. Each class has one method.
- Premature generalization: Building a plugin system for a feature that will only ever have one implementation.
Pragmatic guidelines:
- YAGNI (You Ain't Gonna Need It): Don't add abstractions for future flexibility until the second or third case demands it. Extract an interface when the second implementation appears, not before.
- Rule of Three: Wait until you see three instances of a pattern before abstracting. The first two implementations might differ in unexpected ways.
- Readability trumps purity: If following a principle makes the code harder to understand, the principle is costing more than it's worth. Code is read far more often than written.
- Context matters: A startup prototype and a 10-year banking system have different needs. Apply SOLID more rigorously to core domains and long-lived systems. Be more relaxed with utility code, scripts, and throwaway prototypes.
The senior engineer's approach: write simple code first. When you feel pain (difficult testing, frequent modification of the same file, rippling changes), identify which SOLID principle would help, and refactor. Let the need drive the design.
See our career path to staff engineer and system design interview guide.
Follow-up questions:
- Can you give an example where following SOLID strictly would be the wrong choice?
- How do you decide between a pragmatic shortcut now and a principled design for the future?
- How do you communicate design trade-offs to junior engineers?
10. How does the Dependency Inversion Principle enable hexagonal architecture?
What the interviewer is really asking: Can you connect SOLID principles to architectural patterns? This tests architectural thinking.
Answer framework:
Hexagonal architecture (Ports and Adapters, coined by Alistair Cockburn) is DIP applied at the architectural level. The application core (business logic) defines ports (interfaces). External systems connect through adapters that implement these ports.
Structure:
- Core domain (center): Business logic with zero dependencies on infrastructure. Defines ports (interfaces) for everything it needs from the outside world.
- Ports (interfaces): Inbound ports define what the application can do (use cases). Outbound ports define what the application needs (data access, messaging, external services).
- Adapters (outer ring): Implement the ports. An HTTP adapter maps REST requests to inbound port calls. A PostgreSQL adapter implements the outbound repository port. A Kafka adapter implements the outbound messaging port.
DIP enables this: the domain layer defines the outbound ports (abstractions). The infrastructure layer implements them (details depend on abstractions). The dependency arrow always points inward — infrastructure depends on the domain, never the reverse.
Benefits: domain logic is framework-agnostic (not coupled to Spring, Express, or any framework), fully testable with in-memory adapters, portable (switch databases by swapping adapters), and focused (domain logic doesn't deal with HTTP, SQL, or serialization).
In practice:
This architecture is increasingly adopted at companies building complex domain logic, including financial systems, healthcare platforms, and enterprise applications. See our backend development crash course and system design interview guide.
Follow-up questions:
- How does hexagonal architecture differ from clean architecture and onion architecture?
- When is hexagonal architecture over-engineering?
- How do you handle cross-cutting concerns (logging, transactions) in hexagonal architecture?
11. How do SOLID principles affect database design?
What the interviewer is really asking: Can you apply SOLID thinking beyond code to data modeling?
Answer framework:
SOLID principles can inform database design, though the mapping is less direct than in application code:
SRP in database design: Each table should represent one entity or concept. A users table that contains authentication data, profile information, billing details, and notification preferences violates SRP. Split into users (core identity), user_profiles (display info), user_billing (payment data), user_preferences (notification settings). Each table changes for different business reasons.
OCP in database design: Design schemas that can be extended without modifying existing tables. Use junction tables for many-to-many relationships. Use JSON/JSONB columns for flexible metadata. Use table inheritance (PostgreSQL) or type columns with the EAV (Entity-Attribute-Value) pattern for polymorphic data.
ISP in database design: Create views for different consumers. Instead of granting access to the full orders table, create customer_order_summary (minimal data for the customer portal), internal_order_details (full data for internal tools), and analytics_orders (aggregated data for the analytics team). Each consumer sees only what it needs.
DIP in database design: Application code depends on repository interfaces, not SQL directly. Schema changes are absorbed by the repository implementation. The domain model (in-memory object) may differ from the database schema — the repository handles the mapping. This is the Data Mapper pattern (Hibernate, SQLAlchemy) vs Active Record (Rails).
See our PostgreSQL interview questions and system design interview guide.
Follow-up questions:
- How does the Data Mapper pattern embody DIP?
- When does normalizing a database for SRP hurt query performance?
- How do you handle schema evolution while maintaining OCP?
12. How do SOLID principles apply to API contract design?
What the interviewer is really asking: Can you apply these principles to the boundaries between systems, not just within a system?
Answer framework:
SRP: Each API endpoint should have one responsibility. POST /orders creates an order. POST /orders/{id}/cancel cancels an order. Not POST /orders with a action field that switches between "create", "cancel", "refund", and "modify."
OCP: APIs should be extensible without breaking changes. Add new optional fields to responses without removing existing ones. Add new endpoints for new functionality rather than modifying existing endpoints. Use API versioning (/v1/, /v2/) for breaking changes.
LSP: If your API promises a contract (schema, behavior), all implementations must honor it. A mock server used in testing must behave identically to the production server for the same inputs. Different service versions behind a load balancer must return compatible responses.
ISP: Provide focused APIs for different consumers. A mobile app needs a lightweight response (fewer fields, smaller payloads). An admin dashboard needs a detailed response. Use field selection (?fields=id,name,status) or provide separate endpoints rather than one endpoint that returns everything.
DIP: Consumers depend on the API contract (OpenAPI spec, GraphQL schema, Protobuf definition), not the implementation. The contract is the abstraction. The server implementation is the detail. Schema-first design (define the contract, then implement) embodies DIP.
See our backend development crash course and system design interview guide.
Follow-up questions:
- How do you handle backward compatibility when evolving an API?
- What is the difference between schema-first and code-first API design?
- How does GraphQL embody ISP compared to REST?
13. How do you teach SOLID principles to a team?
What the interviewer is really asking: Senior and staff engineers are expected to elevate their team. Can you teach and mentor effectively?
Answer framework:
Teaching SOLID principles effectively requires practical examples from the team's codebase, not textbook examples.
Start with pain points: Don't start with theory. Identify specific problems the team experiences: "We keep breaking feature X when modifying feature Y" (SRP violation), "Adding a new payment method requires changes in 15 files" (OCP violation), "We can't unit test the OrderService without running PostgreSQL" (DIP violation). Connect the principle to the pain.
Code review as teaching: During code reviews, point out principle violations with specific, constructive suggestions. Instead of "this violates SRP," say "this class handles both order validation and email sending. If we split these, we can test validation without mocking the email service."
Refactoring workshops: Take a real piece of the team's code and refactor it together. Show the before and after. Discuss the trade-offs. This is more effective than slides or articles because it uses familiar context.
Incremental adoption: Don't enforce all five principles at once. Start with the one that addresses the biggest pain point. For most teams, DIP (enabling testability) and SRP (reducing change coupling) have the highest immediate impact.
Avoid dogmatism: Emphasize that SOLID principles are guidelines, not rules. Show examples where over-applying a principle made code worse. Encourage the team to discuss trade-offs during design reviews.
See our career path to staff engineer and system design interview guide.
Follow-up questions:
- How do you handle pushback from team members who see SOLID as over-engineering?
- What is the most impactful SOLID principle for a team that doesn't test their code?
- How do you measure whether applying SOLID principles has improved code quality?
14. How do SOLID principles relate to software architecture patterns?
What the interviewer is really asking: Can you see the connection between code-level principles and system-level architecture?
Answer framework:
SOLID principles are the foundation that architectural patterns build upon:
Microservices architecture embodies SRP: Each service has one bounded context. But SRP alone doesn't tell you where to draw service boundaries — you need Domain-Driven Design (DDD) for that.
Plugin architectures embody OCP: The host application defines extension points. Plugins extend behavior without modifying the host. VS Code, Webpack, and Kubernetes operators follow this pattern.
Event-driven architecture embodies DIP: Services depend on event schemas (abstractions), not on other services (implementations). The event bus inverts the dependency — producers and consumers don't know about each other.
Hexagonal architecture embodies DIP: As discussed, ports and adapters are a direct application of DIP at the architectural level.
CQRS (Command Query Responsibility Segregation) embodies ISP: Read and write operations have different interfaces. Commands modify state through one interface. Queries read state through another. Consumers use only the interface they need.
Clean Architecture (Robert C. Martin) embodies all five: Concentric circles with dependency rules. Inner circles don't know about outer circles (DIP). Each layer has one responsibility (SRP). The architecture is testable, framework-independent, and database-independent.
The key insight: SOLID principles at the code level create designs that naturally compose into clean architectures at the system level. If your code follows SOLID, your architecture will be more modular, testable, and evolvable.
See our distributed systems guide, microservices concepts, and system design interview guide.
Follow-up questions:
- How does domain-driven design complement SOLID principles?
- What is the relationship between coupling, cohesion, and SOLID?
- Can a system follow SOLID principles but still have a bad architecture?
15. Walk through a real-world refactoring using SOLID principles.
What the interviewer is really asking: Can you apply all five principles together in a practical scenario?
Answer framework:
Scenario: an e-commerce OrderProcessor class that handles order creation, inventory checking, payment processing, email notifications, and analytics tracking — all in one 500-line class.
Step 1 — Identify SRP violations: the class changes for five different reasons (business logic, inventory rules, payment integration, email templates, analytics events). Each is a separate responsibility.
Step 2 — Extract responsibilities:
OrderService— orchestrates the order workflowInventoryChecker— validates stock availabilityPaymentProcessor— handles payment via gatewayOrderNotifier— sends confirmation emailsAnalyticsTracker— records events for analytics
Step 3 — Apply DIP: define interfaces (InventoryPort, PaymentPort, NotificationPort, AnalyticsPort). OrderService depends on these interfaces, not concrete implementations. Concrete implementations (StripePaymentAdapter, SendGridNotifier) implement the ports.
Step 4 — Apply OCP: when a new payment method is needed, create a new adapter implementing PaymentPort. When a new notification channel is needed (SMS, push), create a new implementation of NotificationPort. No existing code changes.
Step 5 — Apply ISP: the PaymentPort interface has only processPayment() and refund(). The NotificationPort has only sendOrderConfirmation(). No fat interfaces.
Step 6 — Verify LSP: all PaymentPort implementations (Stripe, PayPal, Square) honor the same contract — same method signatures, same error behavior, same guarantees.
Result: each class is small and focused. OrderService is easily tested with mock implementations of each port. Adding new payment methods, notification channels, or analytics integrations requires zero changes to existing code.
See our design patterns guide, backend development crash course, and system design interview guide.
Follow-up questions:
- How do you handle the orchestration logic in
OrderService— is it too much responsibility? - How do you manage transactions across multiple services after the refactoring?
- What testing strategy do you use for each extracted component?
Common Mistakes in SOLID Interviews
-
Reciting definitions without examples — Every principle needs a concrete example, preferably from your own experience.
-
Not discussing trade-offs — SOLID has costs (more files, more indirection). Senior engineers discuss when the cost is worth it.
-
Applying SOLID dogmatically — Extracting an interface for every class, even with a single implementation, adds complexity without benefit.
-
Confusing DIP with DI — DIP is the principle (depend on abstractions). DI is one technique for achieving it. They're related but distinct.
-
Ignoring language context — SOLID looks different in Go (no inheritance), Python (duck typing), and Rust (traits). Adapt your examples to the language being discussed.
-
Not connecting to architecture — SOLID principles scale from classes to modules to services. Demonstrating this connection shows senior-level thinking.
How to Prepare
Week 1: Study each SOLID principle with real-world examples. Find violations in codebases you work with.
Week 2: Practice refactoring. Take a class with multiple responsibilities and decompose it using SOLID principles.
Week 3: Study how SOLID applies to different paradigms (OOP, FP) and different languages (Java, Go, TypeScript).
Week 4: Connect SOLID to architectural patterns (hexagonal architecture, microservices, event-driven design). Practice articulating trade-offs.
For comprehensive preparation, see our system design interview guide and explore the learning paths. Check our pricing plans for full access.
Related Resources
GO DEEPER
Master this topic in our 12-week cohort
Our Advanced System Design cohort covers this and 11 other deep-dive topics with live sessions, assignments, and expert feedback.