Module 01 · Foundations · All tracks
OOP & Programming Foundations
Every interviewer assumes you know this — which is exactly why fumbling it hurts so much. This module rebuilds the foundations so you understand why each idea exists, not just what it's called.
By the end you'll be able to explain, with conviction:
- The four pillars as four angles on a single idea — and defend each one's purpose.
- SOLID and the common design patterns as tools, with honest tradeoffs.
- Where complexity, concurrency and language features fit — enough to never freeze.
1Why OOP exists
Before you can defend object-orientation, you have to remember the pain it was invented to cure.
Picture a program written as a pile of functions over a pile of shared data — early procedural code. At a thousand lines it's fine. At a hundred thousand, a quiet catastrophe sets in: any function might touch any data, so to change one structure safely you'd have to read the entire codebase to find every place that depends on it. The data and the logic that owns it have drifted apart, and nobody can hold the whole thing in their head anymore.
Object-orientation is one disciplined response to that. The core move is almost embarrassingly simple: keep data and the behaviour that operates on it in the same place, behind a boundary. An Account object owns its balance and the rules for changing it. Nothing outside can reach in and set the balance to a negative number, because the only way in is through methods that enforce the rules.
Everything else — the famous four pillars — is a consequence of taking that boundary seriously. Encapsulation is the boundary. Abstraction is what you choose to show through it. Inheritance and polymorphism are how related objects share and vary behaviour across that boundary. Hold onto that framing: the pillars aren't four unrelated vocabulary words, they're four views of "manage complexity by drawing boundaries around state and behaviour."
Why it matters
OOP is not the only paradigm — functional programming manages complexity differently, by avoiding shared mutable state entirely. Knowing what problem OOP solves lets you answer the senior-level follow-up: "When would you not reach for OOP?"
Interview angle
"OOP is fundamentally about managing complexity — you keep data together with the behaviour that owns it, so a large system stays understandable and safe to change as it grows. The four pillars are just four facets of that one boundary-drawing idea."
2Encapsulation — the boundary itself
Encapsulation is two things at once: bundling state with the methods that act on it, and hiding that state behind a controlled interface. The hiding is the part that earns its keep.
Think of a class as a sealed appliance. There are buttons on the front — the public methods — and wiring inside that you never touch. As long as the buttons keep behaving the same way, the manufacturer can completely redesign the internals and you'd never know. That's the entire value proposition: callers depend on the public contract, never on the internals, so the internals stay free to change.
The opposite — exposing raw fields — quietly poisons a codebase. The moment a field is public, anyone might read or write it, and you've lost the ability to validate, to compute it lazily, to log access, or to change its representation. You've also lost the single place where an invariant could be enforced. Encapsulation gives every object exactly one doorway, so the rules live at the doorway.
class Account {
private long balanceCents; // hidden state
public void withdraw(long cents) { // the only doorway
if (cents <= 0) throw new IllegalArgumentException("amount must be positive");
if (cents > balanceCents) throw new IllegalStateException("insufficient funds");
balanceCents -= cents; // invariant enforced in one place
}
public long balance() { return balanceCents; }
}
The balance can never go negative, because there's no path to it that skips the check.
Common trap
Generating a getter and setter for every private field is not encapsulation — a public setter is just a public field with extra steps. Real encapsulation exposes intent (withdraw), not raw mutation (setBalance).
Interview angle
"Encapsulation lets me change the inside of a class freely as long as the public contract holds. It also gives me one place to enforce invariants — which is what makes large codebases safe to evolve."
3Abstraction — deciding what to show
Encapsulation and abstraction are cousins that get muddled constantly, so pin the distinction now: encapsulation hides internals to protect them; abstraction hides them to simplify what you have to think about. One is about safety, the other is about cognitive load.
Abstraction is the art of deciding what to leave out. A PaymentGateway exposes one meaningful capability — charge(amount) — and hides whether that involves Stripe, a retry loop, currency conversion, or a fraud check. The caller depends on the promise ("this charges money") and never on the mechanism. Because the surface is small and stable, you can wield something genuinely complex without studying its guts.
The discipline that makes abstraction work is naming by intent, not by mechanism. Call it PaymentGateway, not StripeHttpClientWrapper. The first name survives when you switch providers; the second leaks the very detail abstraction was supposed to hide. Good abstractions read like a vocabulary of what your system does, with the how tucked safely behind each word.
Go deeper
The best abstractions are ones where the cost of a wrong guess is low. A leaky abstraction (one that forces you to understand its internals to use it correctly) is often worse than no abstraction — see Joel Spolsky's "Law of Leaky Abstractions." Aim for boundaries that genuinely let you forget what's behind them.
Interview angle
"Abstraction is about exposing intent, not mechanism. Encapsulation hides state to protect invariants; abstraction hides complexity so I can depend on what something does without caring how it does it."
4Inheritance vs composition
Inheritance lets one type declare it is a kind of another and inherit its fields and behaviour for free. A SavingsAccount is an Account, so it reuses the balance and deposit logic and only specialises how interest accrues. Used on a genuine "is-a" relationship, it removes duplication and lets you treat a whole family of types uniformly.
The problem is that inheritance is seductive as a code-reuse shortcut, and that's where it turns toxic. The instant you extend a class just because it happens to have a method you want, you've welded your type to its parent's entire shape. A change three levels up the hierarchy can break a child that looked unrelated — the "fragile base class" problem. Deep inheritance trees become the kind of code people are afraid to touch.
The senior default is "favour composition over inheritance." Instead of being a thing to reuse its behaviour, hold a thing and delegate to it. Composition models a "has-a" relationship, keeps coupling loose, and lets you swap the held object at runtime — exactly the flexibility a rigid hierarchy denies you.
Common trap
The classic broken hierarchy: making Square extends Rectangle. A square that inherits setWidth/setHeight can be put into an invalid state by code that only knows it as a rectangle. That violation has a name — it's the "L" in SOLID, coming up next.
Interview angle
"I use inheritance only for true is-a relationships. When I just want to reuse behaviour I reach for composition first — it keeps coupling loose and lets me swap collaborators at runtime, whereas deep hierarchies get brittle fast."
5Polymorphism
Polymorphism — "many forms" — is the pillar that makes the others pay off. The idea: one call, many behaviours, resolved at runtime. You ask every Shape for its area(); the circle runs circle maths, the square runs square maths, and the calling code never asks which one it's holding.
That indifference is the whole prize. Your logic is written once, against the abstract type, and it keeps working when you add a Triangle next month — without editing a single line of the caller. New behaviour arrives by adding a class, not by editing existing code. If that sounds familiar, it should: polymorphism is the engine behind "open for extension, closed for modification."
It's worth distinguishing the flavours so you can speak precisely. Runtime (subtype) polymorphism is the dynamic dispatch above — the interviewer almost always means this. Compile-time polymorphism covers method overloading and generics, resolved before the program runs. Mentioning that you know the difference is a small, cheap seniority signal.
Shape. Adding Triangle needs zero changes to code that already consumes shapes.Interview angle
"Polymorphism lets me write code against an abstraction once and add new implementations later without touching the callers. It's the mechanism that turns 'open/closed' from a slogan into something real."
6SOLID — five smells, one idea
SOLID is five design principles, but underneath they collapse into one goal: reduce coupling so a change in one place doesn't ripple through the whole system. Treat them as smells and warning signs, not laws to obey mechanically.
S — Single Responsibility
A class should have one reason to change. If your Report class both computes numbers and formats them as PDF, a change to either concern forces you to reopen the same file and risks breaking the other. Split by reason-to-change, and each piece stays small and testable.
O — Open/Closed
Software should be open to extension but closed to modification. Concretely: you should be able to add new behaviour by adding code, not by editing working code. Polymorphism (§5) is how you achieve it — a new subtype instead of another branch in a growing switch.
L — Liskov Substitution
A subtype must be usable anywhere its parent is, without surprises. The Square extends Rectangle trap breaks this: substituting a square where a rectangle is expected can violate the caller's assumptions. If a subclass has to weaken a guarantee, it isn't really a subtype.
I — Interface Segregation
Many small, focused interfaces beat one fat one. Don't force a Printer to implement scan() and fax() just because some machines do all three. Clients should depend only on the methods they actually use.
D — Dependency Inversion
Depend on abstractions, not concretions. High-level policy shouldn't import a specific database class; both should depend on an interface. This is what makes code testable (swap in a fake) and flexible (swap in a new implementation).
Common trap
Reciting SOLID as five definitions reads as memorised. The stronger move is to show the thread: every principle is a different lever on the same goal — keep things loosely coupled so change stays local. Lead with that, then illustrate one with a concrete example.
Interview angle
"SOLID is really one idea expressed five ways — reduce coupling so a change in one place doesn't cascade. I treat them as smells: if a class has two reasons to change I split it; if a switch grows a branch per type, I invert the dependency and use polymorphism."
7Design patterns — a shared vocabulary
A design pattern is a named, reusable solution to a problem that keeps recurring across many systems. Their value is twofold: you skip reinventing (and re-debugging) a solution someone already refined, and you gain a shared vocabulary — "let's use a Strategy here" communicates an entire design in three words.
The trap is treating patterns as a goal. Reaching for them to look sophisticated adds ceremony and indirection without buying anything. The discipline is to recognise the recurring problem first; the pattern is the answer to it, never the starting point. Here are the four you'll be asked about most.
Strategy — swap an algorithm
When behaviour needs to vary — different pricing rules, different sort orders — Strategy puts each variant behind a common interface and lets you inject the one you want. It kills the sprawling if/else and lets behaviour change at runtime.
Order holds a strategy and delegates. Swap Regular for BlackFriday without touching Order.Factory — centralise creation
A Factory hides object creation behind a method, so callers say create(type) instead of hard-coding which concrete class to new. Adding a new type touches one place. Factory decides what to build; Strategy decides how to behave — a clean pair to contrast.
Observer — decouple events from reactions
Observer lets objects subscribe to another's events. The publisher fires "something changed" and never knows who's listening; subscribers react independently. That decoupling of "it happened" from "who cares" is the real prize — it's the backbone of event systems and UI frameworks.
Singleton — exactly one instance
Singleton guarantees a single shared instance — reasonable for a genuinely global resource like configuration or a connection pool. Treat it with suspicion everywhere else: global mutable state is hard to test and easy to abuse. Knowing when not to use it is the senior signal.
Interview angle
"Patterns are a shared vocabulary for recurring problems — Strategy to swap algorithms, Factory to centralise creation, Observer to decouple events from reactions. They're useful when they fit and harmful when I reach for them just to look clever, so I always start from the problem."
8DRY · KISS · YAGNI — and pragmatism
These three are the everyday hygiene of clean code. They're quick to state, but the senior version is knowing their limits.
- DRY — Don't Repeat Yourself. Every piece of knowledge should have one authoritative home; duplication is a future bug waiting for someone to update one copy and forget the other.
- KISS — Keep It Simple. The simplest solution that works beats the clever one that impresses. Cleverness is a tax your future teammates pay.
- YAGNI — You Aren't Gonna Need It. Don't build for imagined future requirements; you'll usually guess wrong and carry the complexity forever.
Common trap
DRY taken too far creates the wrong abstraction — forcing two things that merely look alike to share code couples them so the next change to one breaks the other. "A little duplication is far cheaper than the wrong abstraction" (Sandi Metz). Naming this tension is a strong signal.
Interview angle
"DRY, KISS and YAGNI keep code clean, but I hold them loosely — premature DRY can be worse than a little duplication, because the wrong abstraction is expensive to unwind."
9Complexity & data-structure awareness
You won't be inventing algorithms in most interviews outside a dedicated coding screen — but freezing when asked "what's the cost of that?" reads as junior. The goal here is fluent awareness, not mastery.
Big-O describes how cost grows as input grows, ignoring constants. O(1) is flat, O(log n) barely moves, O(n) scales linearly, O(n log n) is good sorting, and O(n²) is the nested-loop smell that melts at scale. You're judged less on perfect analysis than on whether you think about cost at all.
Every data structure trades one operation for another — pick by access pattern:
| Structure | Buys you | Reach for it when |
|---|---|---|
| Hashmap / set | O(1) average lookup, insert | You need fast keyed access or membership tests |
| Array / list | O(1) indexed access, cache-friendly | Order matters and you index by position |
| Tree / sorted set | O(log n) ordered ops, ranges | You need sorted order or range queries |
| Queue / stack | O(1) ends, FIFO / LIFO | Processing order is the whole point |
Interview angle
"I pick structures by access pattern — a hashmap when I need fast lookups, a tree when I need order — and I can always put a Big-O on the hot path. That cost-awareness is usually what the question is really probing."
Go deeper
The actual algorithm practice — patterns, live problem-solving, the spoken script — lives in the optional Module 19 · DSA & Algorithms. This section is just the awareness layer so you never stall.
10Concurrency & thread safety
Almost every concurrency bug traces back to a single villain: shared mutable state. When two threads read and write the same data without coordination, the result depends on who happened to get there first — a race condition. The reason they're so vicious is that they're intermittent: the code passes a thousand times and corrupts data on the thousand-and-first.
Interviewers aren't fishing for lock-free wizardry. They want to hear that you understand the danger and reach for the right tool. And the senior instinct is to remove the villain before fighting it:
- Don't share what you don't have to — give each thread its own data.
- Make it immutable where you can — data that never changes is trivially thread-safe.
- Guard the rest with locks or atomics when shared mutation is unavoidable — and keep the guarded section small.
- Prefer higher-level tools — thread pools, concurrent collections, message queues — over hand-rolled locking, which is where deadlocks breed.
Common trap
"I'll just add a lock everywhere" trades a correctness bug for a performance bug (and risks deadlock). The order you reach for tools matters: immutability and not-sharing first, locks last.
Interview angle
"My first move with concurrency is to avoid shared mutable state — immutability and confinement remove whole classes of bugs. Locks and atomics are the fallback for state I genuinely must share, not the starting point."
11Everyday language fluency
These aren't deep topics, but fumbling them reads as rust — so be crisp. Each exists to let the language carry correctness you'd otherwise hand-write and get wrong.
Exceptions
Exceptions signal the genuinely exceptional and separate error-handling from the happy path. The rule: don't use them for ordinary control flow (that's both slow and confusing), and catch only what you can meaningfully handle — swallowing exceptions silently is how bugs go dark.
Generics
Generics let you write reusable, type-safe containers and functions — a List<Account> the compiler guarantees holds accounts. The payoff is that type errors are caught at compile time instead of blowing up at runtime, and you stop casting and praying.
Streams & lambdas
Lambdas are inline functions; streams (or LINQ, or comprehensions) let you express operations on data declaratively — map, filter, reduce — instead of hand-writing the loop. You say what you want, not the bookkeeping of how to iterate, and the intent reads off the page.
Interview angle
"I lean on the type system and declarative operations — generics let the compiler catch whole categories of errors, and streams let me say what I want done to a collection rather than hand-rolling the loop. Both move correctness from my head into the language."
Recap — what you can now teach
- The four pillars are four views of one idea: draw a boundary around state and the behaviour that owns it.
- Encapsulation protects, abstraction simplifies — both hide internals, for different reasons.
- Favour composition over inheritance; reserve inheritance for true is-a relationships.
- Polymorphism is the engine of open/closed — extend by adding types, not editing callers.
- SOLID is one goal five ways: reduce coupling so change stays local.
- Patterns are a vocabulary; start from the recurring problem, never the pattern.
- Pick data structures by access pattern; avoid shared mutable state before reaching for locks.
Self-check
No scoring — say each answer out loud before revealing it. If you can't, re-read that section.
In one sentence, what single problem does OOP exist to solve?
Managing complexity — by keeping data together with the behaviour that owns it behind a boundary, so large systems stay understandable and safe to change.
What's the precise difference between encapsulation and abstraction?
Both hide internals, but encapsulation hides them to protect invariants (safety), while abstraction hides them to reduce what the caller must think about (simplicity).
Why is "favour composition over inheritance" the common default?
Inheritance welds a type to its parent's whole shape (fragile base class) and is fixed at compile time; composition keeps coupling loose and lets you swap collaborators at runtime.
What single idea unifies all five SOLID principles?
Reduce coupling so a change in one place doesn't ripple through the system — each principle is a different lever on that one goal.
What's your first instinct for making concurrent code safe, before locks?
Eliminate shared mutable state — don't share data, or make it immutable. Locks and atomics are the fallback for state you genuinely must share.