TheHowPage
Interactive Explainer

Low Level Design OOP & SOLID Principles Visualized

Every bug you've ever written was a design problem in disguise.

UserServiceAuthDBEmailSenderLoggerConfigAPIHandler

Spaghetti architectureClean, SOLID design

Scroll to explore ↓

The Blueprint Before the Code

Before you write a single line of code, you need a plan. In software, that plan has two levels. High Level Design (HLD) decides the big picture — which services do we need? Which database? How do they talk to each other? Low Level Design (LLD) zooms in on each piece — what are the actual building blocks inside a service, how do they connect, and what rules do they follow?

Think of it like building a house. HLD is the architect saying “we need a kitchen, two bedrooms, and a bathroom.” LLD is the engineer deciding where every pipe goes, which wire connects to which outlet, and how the plumbing doesn't leak. Skip LLD, and your house has water in the wrong places.

In code, those building blocks are called classes — think of a class as a template or blueprint for creating things. A User class is a blueprint for every user in your app. A PaymentProcessor class is a blueprint for handling payments. LLD is the art of deciding which classes you need, what each one does, and how they work together.

85%

of top tech companies

have a dedicated LLD interview round — Amazon, Google, Microsoft, Uber, Flipkart

1994

the rules were written

The Gang of Four (4 computer scientists) published the Design Patterns book — still relevant 30 years later

4+5

core concepts

4 OOP pillars + 5 SOLID principles — we'll explain every one from scratch

Terms you'll see in this chapter

Class —
A blueprint for creating objects. Think “cookie cutter.” The class is the cutter, each cookie is an object (also called an instance).
Object —
A specific thing created from a class. If Dog is the class, your pet “Buddy” is the object.
Method —
A function that belongs to a class. dog.bark() — bark is a method on the Dog class.
Interface —
A contract that says “any class that implements me must have these methods.” Like a job description — it says what you must do, not how you do it.
OOP —
Object-Oriented Programming. A way of organizing code around objects (data + behavior) instead of just functions. Used by Java, Python, TypeScript, C#, and most modern languages.

This chapter teaches everything from scratch: the 4 pillars of OOP, the 5 SOLID principles, how to measure code quality, and how to sketch designs on a whiteboard. No prior knowledge assumed. Let's start with the foundations.

Encapsulation

Bundle data with the methods that operate on it — and hide the internals.

Anyone can touch balance directly
BankAccount.tsBROKEN
TypeScript
1class BankAccount {
2 public balance = 100
3}
4
5// External code:
6account.balance = -500
7// ✘ Balance is now -500!

Any code anywhere can set balance to anything — negative, NaN, Infinity. No validation, no audit trail, no protection.

Note: This is why JavaScript added #private fields in ES2022.

You Already Use These Every Day

These four pillars aren't textbook abstractions — they show up everywhere, even if you've never heard the words. Every React component is encapsulation: it owns its own data and decides what to show the outside world. Every time you call fetch() without knowing how HTTP works underneath, that's abstraction. Every time two different objects respond to the same function call differently — that's polymorphism.

But the fourth pillar — inheritance (where one class “inherits” all the behavior of another, like how a Dog inherits traits from Animal) — is the most controversial. It sounds elegant. In practice, it causes so many problems that two of the most important programming languages of the last decade — Go and Rust — looked at 30 years of inheritance in Java and C++ and said: “We're not doing this.”

Let's see why inheritance breaks — and what replaced it.

Why Modern Languages Said No to Inheritance

Quick recap: inheritance means one class automatically gets all the properties and methods of another class. A Dog class that inherits from Animal automatically knows how to eat() and sleep() without you writing those methods again. Sounds great — less code, less repetition.

The problem shows up when the hierarchy gets deep. Two specific bugs keep appearing:

  • 1.The Fragile Base Class problem — you change one method in the parent class (Animal), and suddenly every child class (Dog, Cat, Bird) breaks in ways you can't predict. The parent is “fragile” because children depend on its every detail.
  • 2.The Diamond Problem — class D inherits from both B and C, and both B and C inherit from A. When D calls a method that exists in A, which version does it get — the one that came through B, or through C? There's no good answer. C++ tried to solve this and made things worse. Java just banned it.

Go (2009)

Rob Pike and Ken Thompson designed Go after watching inheritance cause cascading failures in Google's massive Java and C++ codebases. Go uses struct embedding and interfaces — you compose behavior, never inherit it.

Rust (2015)

Rust uses traits (similar to interfaces) and composition. No inheritance at all. As the Rust team put it: “Inheritance has recently fallen out of favor as a programming design solution, because it's too easy to share more code than necessary.”

Java still has inheritance. Java also has AbstractSingletonProxyFactoryBean. Coincidence?

The Diamond Problem — Visualized

Why Multiple Inheritance Breaks

A.method()BCD

Class D inherits from both B and C, which each inherit from A. Hit the button to see what happens when D tries to call A's method.

The Fix: Composition Over Inheritance

The Gang of Four said it in 1994. The industry is still learning.

InheritanceComposition
Duck.tsERROR
TypeScript
1class Walker { walk() { ... } }
2class Swimmer { swim() { ... } }
3class Flyer { fly() { ... } }
4
5class Duck extends Walker, Swimmer, Flyer {
6 // ← ERROR: multiple inheritance!
7}

Most languages don't even allow this. And even if they did, changing Walker breaks Duck. Every subclass is a bet that the parent never changes. It always loses.

Coupling

High

Flexibility

Low

Testability

Hard

SOLID — Five Rules That Prevent Bad Design

Robert C. Martin (Uncle Bob) introduced SOLID in the early 2000s. Twenty years later, Stack Overflow still calls them “the foundation for modern software architecture.” They're not academic theory — they're a checklist for code that won't make your teammates cry during code review.

LetterPrincipleIn one sentence
SSingle ResponsibilityA class should have only one reason to change.
OOpen/ClosedOpen for extension, closed for modification.
LLiskov SubstitutionSubclasses must be substitutable for their parent classes.
IInterface SegregationNo class should be forced to depend on methods it doesn't use.
DDependency InversionDepend on abstractions, not concrete implementations.

Each principle is simple to state and tricky to apply. The interactive lab below lets you toggle violations on and off — and watch three metrics change:

  • Complexity — how many things a class is trying to do. Lower is better.
  • Lines to change — how much existing code you need to modify to add a feature. Zero is the goal.
  • Testability — can you test this class in isolation, without needing a real database or API? Yes means your tests run in milliseconds, not minutes.

The SOLID Violations Lab

Toggle each violation on and off. Watch the metrics change.

SSingle Responsibility
UserService.tsVIOLATED
TypeScript
1class UserService {
2 authenticate(user, password)
3 sendEmail(to, subject, body)
4 generateReport(userId, format)
5 saveToDb(userData)
6 validateInput(fields)
7 logActivity(action, timestamp)
8 hashPassword(raw)
9 formatCurrency(amount)
10 sendSMS(phone, message)
11 compressImage(file)
12 parseCSV(raw)
13 exportPDF(report)
14}
15// 12 methods, 1 class. 6 reasons to change.

Complexity

47/ 50

Lines to change

12

for a new feature

Testable

No

The GameManager class — handles players, rendering, input, file I/O. One change to rendering breaks save files.

When SOLID Violations Ship to Production

SOLID violations aren't just interview trivia. They cause real bugs in real software — bugs that passed code review, passed tests, and made it to production.

Here are two famous examples. Both involve the Liskov Substitution Principle (the “L” in SOLID) — the rule that says if you replace a parent class with one of its children, everything should still work. Sounds obvious. It isn't.

Java's TreeSet — A Liskov Violation in the JDK

Set uses equals() to check duplicates. SortedSet (implemented by TreeSet) uses compareTo(). Swap a HashSet for a TreeSet — items silently disappear because the comparison contract is different. Developers wasted weeks debugging this. The JDK documentation even acknowledges: “The behavior of a sorted set is well-defined even if its ordering is inconsistent with equals; it just fails to obey the general contract of the Set interface.”

Source: Java SE Documentation, documented by codelord.net

NLog Framework — Silent LSP Bug

The NLog .NET logging framework had a subclass (NLogViewerTarget) that silently ignored the Layout property when set. If you passed an NLogViewerTarget where a regular target was expected, your custom layout was discarded — and the logs looked wrong with no error message. A textbook Liskov violation: the subtype broke the parent's contract silently.

Source: devonblog.com — SOLID violations in the wild

Two Numbers That Predict Code Quality

Beyond SOLID, there are two metrics that experienced developers check instinctively:

Coupling (want this LOW)

How much one class depends on other classes. If changing UserService forces you to also change EmailService, Logger, and Database — that's high coupling. One change cascades everywhere. Nightmare.

Cohesion (want this HIGH)

How related the methods inside a single class are. If every method in PaymentService deals with payments, that's high cohesion — good. If it also sends emails and generates PDFs, it's doing too many things — low cohesion. That's a God Class.

The ideal: each class does one thing well (high cohesion) and doesn't depend on the internals of other classes (low coupling). Toggle between the God Class and refactored versions below to see the difference.

Coupling vs Cohesion — The Two Numbers That Matter

Toggle between a bloated God Class and its refactored version. Watch the metrics flip.

Coupling: 8 (high)Cohesion: 2 / 10 (low)

OrderManager

createOrder()validatePayment()sendEmail()updateInventory()generateInvoice()logActivity()authenticateUser()applyDiscount()calculateTax()notifyWarehouse()updateDashboard()exportCSV()

8 dependencies →

Database
EmailService
PaymentGateway
InventoryAPI
Logger
AuthProvider
TaxCalculator
WarehouseAPI
Red flag: If the class name contains Manager, Controller, Handler, or Util — it's probably a God Class.

Coupling = how many other classes this class depends on. Low is better.

Cohesion = how related the methods inside a class are. High is better.

SonarQube measures cohesion with LCOM4. If methods in a class form disconnected groups (they don't share fields), the class should be split.

How to Spot a God Class

Every codebase has at least one — a class that started small and grew over months until it became the center of everything. You know the warning signs: the file is 500+ lines, the class name contains “Manager”, “Controller”, “Handler”, or “Utils”, and every new feature somehow touches it.

LinearB (a developer analytics company) analyzed thousands of real codebases and found that God Classes are the #1 predictor of bugs. Code quality tools like SonarQube can detect them automatically — they look at whether the methods inside a class actually share data with each other. If a class has 3 groups of methods that don't interact at all, it should be 3 separate classes.

The simplest test: describe what your class does in one sentence. If you use the word “and” — “it handles payments and sends emails and logs activity” — it's doing too much. Split it.

Now, knowing when code smells bad is one thing. Communicating your design to others — in interviews, in code reviews, on whiteboards — requires a shared visual language. That language is called UML (Unified Modeling Language). Don't worry — you don't need to master it. You just need to read 5 symbols.

UML — Just Enough to Read a Diagram

You don't need to be a UML expert. You need to survive a whiteboard interview.

Click a card to highlight it in the diagram below.

Mini class diagram — all 5 relationships

111*FleetaddVehicle()dispatch()«abstract»Vehiclemove()«interface»DrivableCardrive()park()Truckhaul()loadCargo()Enginestart()stop()inheritsimplementscompositionaggregation

From Theory to Your Code

You now have the full toolkit: OOP pillars, SOLID principles, coupling and cohesion metrics, and enough UML to sketch a design on a whiteboard. But here's the uncomfortable truth — knowing the principles doesn't mean your code follows them.

Spotify's engineering team runs periodic “code health” reviews where they score their own services against SOLID. Google uses readability reviewers who check for coupling violations before code merges. Even Uncle Bob admits: “The first step is awareness. You can't fix what you don't see.”

The scorecard below is your code health check. Ten yes-or-no questions about your current project. Answer honestly — the score tells you exactly which SOLID principles you're violating and what to fix first.

The SOLID Scorecard — Rate Your Codebase

Answer 10 yes/no questions. Get your SOLID score.

01Does any class in your project have more than 300 lines?

02Do you use if/else or switch to handle different types?

03Can you swap your database without changing business logic?

04Does any interface have methods that some implementors don't use?

05Do any subclasses throw UnsupportedOperationException?

06Can you unit test a class without spinning up a database or API?

07Does any class name contain 'Manager', 'Helper', or 'Util'?

08Are there more than 5 constructor parameters in any class?

09Does changing one class frequently break tests in unrelated classes?

10Can a new team member understand any class in under 5 minutes?

What's Next

You now have the foundation — OOP, SOLID, coupling, cohesion, and UML. The next three chapters teach you the design patterns that implement these principles: Creational patterns (Factory, Builder, Singleton), Structural patterns (Adapter, Decorator, Facade), and Behavioral patterns (Strategy, Observer, Command, State).

After patterns, we apply everything to real interview problems: Parking Lot, Chess, BookMyShow, and a Notification System capstone. Each case study shows how SOLID and patterns work together in a real system.

What's the difference between Low Level Design and High Level Design?

High Level Design (HLD) decides which services, databases, and infrastructure to use — it's the architect's floor plan. Low Level Design (LLD) decides how each service is built internally — the classes, interfaces, relationships, and patterns. In interviews, HLD asks 'design Twitter's architecture' while LLD asks 'design the Tweet class hierarchy and its interactions'. You need both: HLD without LLD is a whiteboard drawing that can't be built, and LLD without HLD is clean code that solves the wrong problem.

Do I need to know SOLID principles for coding interviews?

Yes — 85% of top tech companies (Amazon, Google, Microsoft, Flipkart, Uber, Atlassian) now have a dedicated LLD or machine coding round. Interviewers don't ask 'explain the Open/Closed Principle' — they watch you design a class hierarchy and notice when you create a God Class, use if-else chains instead of polymorphism, or couple classes unnecessarily. Knowing SOLID helps you avoid these red flags without thinking about it. It's the difference between a 'strong hire' and 'mixed signals' in the feedback.

Is inheritance bad? Should I avoid it completely?

Inheritance isn't bad — it's overused. Go (2009) and Rust (2015) dropped inheritance entirely and use composition + interfaces instead. The Gang of Four wrote 'Prefer composition to inheritance' in 1994. The problem is the Fragile Base Class: change a parent class, and every child might break. Use inheritance when you have a genuine 'is-a' relationship that won't change (a Dog is an Animal). Use composition when you have a 'has-a' relationship or when the hierarchy might evolve (a Car has an Engine, and you might swap the engine).

Why do Go and Rust not have inheritance?

Both languages were designed after decades of watching inheritance cause problems in Java and C++. Go's creators (Rob Pike, Ken Thompson) saw that large codebases at Google became fragile due to deep inheritance hierarchies. Go uses struct embedding and interfaces instead — you compose behavior rather than inheriting it. Rust uses traits (similar to interfaces) and composition. Both approaches give you polymorphism without the Diamond Problem, Fragile Base Class, or tightly-coupled hierarchies. The result: flatter, more modular code.

What is a God Class and how do I spot one?

A God Class (or God Object) is a class that does too much — it violates the Single Responsibility Principle by handling multiple unrelated concerns. Warning signs: the class has 300+ lines, 15+ methods, a name containing 'Manager', 'Controller', 'Handler', 'Driver', or 'Util', or changes to it frequently break unrelated features. The classic example is a GameManager that handles players, rendering, input, file I/O, physics, and scoring. SonarQube flags these using the LCOM4 metric (Lack of Cohesion of Methods). Fix: split it into focused classes, each with one reason to change.

Are SOLID principles language-specific?

No — SOLID principles are language-agnostic design principles. They apply to Java, Python, TypeScript, C#, Go, Rust, and any language that supports some form of abstraction. The implementation differs: Java uses interfaces and abstract classes, Go uses interfaces and struct embedding, Rust uses traits, Python uses duck typing and ABCs. But the underlying ideas — single responsibility, depending on abstractions, substitutability — transcend any language. Even functional programmers apply these ideas, just with different vocabulary (pure functions = single responsibility, higher-order functions = dependency inversion).

How do SOLID principles relate to design patterns?

SOLID principles are the 'why', design patterns are the 'how'. The Strategy pattern exists because of the Open/Closed Principle (swap behavior without modifying existing code). The Factory pattern exists because of Dependency Inversion (depend on abstractions, not concrete classes). The Observer pattern follows Single Responsibility (the subject doesn't need to know what observers do). If you understand SOLID deeply, you'll often reinvent design patterns on your own — they're natural solutions to SOLID-compliant design.

What's the difference between coupling and cohesion?

Coupling measures how dependent classes are on each other — low coupling means you can change one class without breaking others. Cohesion measures how related the methods inside a single class are — high cohesion means every method serves the same purpose. The goal is low coupling + high cohesion. SonarQube measures cohesion using LCOM4 (Lack of Cohesion of Methods): if a class has method groups that share no fields, it should be split. A good test: if you describe what a class does and use the word 'and', it probably has low cohesion.

Do microservices follow SOLID principles?

Yes — microservices are SOLID applied at the service level. Each microservice has a Single Responsibility (one bounded context). Services communicate through interfaces/APIs (Dependency Inversion). You can add new services without modifying existing ones (Open/Closed). Any service implementing the same API contract can be swapped (Liskov Substitution). Services expose only the endpoints consumers need (Interface Segregation). This is why SOLID matters beyond classes — it's a fractal principle that scales from methods to classes to services to entire systems.

How do I practice LLD effectively?

Start with the classic interview problems: Parking Lot, Chess, Elevator, BookMyShow, Notification System. For each: (1) List requirements, (2) Identify nouns as classes and verbs as methods, (3) Draw class relationships, (4) Identify which SOLID principles and patterns apply, (5) Code it. Then review open-source code — Lichess (Scala, chess), Signal (Java, messaging), and VS Code (TypeScript, editor) are excellent examples of clean OOP. Practice on platforms like CodeZym or LLDCoding. Most importantly: refactor your own code. Find a God Class in your project and split it.

Sources

Every explainer is free. No ads, no paywall, no login.

If this helped you, consider supporting the project.

Buy us a coffee