CS fundamentals · zero → interview‑ready

Five subjects.
One field manual.

A single page that walks you from the absolute basics to the depth interviewers actually probe — OOP, DBMS (with real SQL), Operating Systems, Computer Networks, and System Design. Built to be read top‑to‑bottom or jumped around mid‑prep.

OOP DBMS + SQL OS Networks LLD / HLD
Key ideaThe one thing you must not forget from a topic.
GotchaThe mistake that trips people up in interviews.
Interview lensWhat they're really asking, with model answers.
// 01 · object-oriented programming

OOP: how objects think

OOP models a program as a society of objects that hold their own data and expose behaviour. Master four pillars, then the two ideas interviewers love to poke at — polymorphism and the subtle line between overloading and overriding.

1.1 The four pillars

Everything else is built on these. Know the one-line definition and a concrete "why".

PillarWhat it meansWhy it matters
AbstractionExpose what an object does, hide how. You drive a car via the pedal, not the combustion.Lets callers depend on an interface, not internals.
EncapsulationBundle data + the methods that guard it; keep fields private behind getters/setters.Invariants stay valid — no one sets age = -5.
InheritanceA subclass reuses and specializes a parent (Dog extends Animal).Share code, model "is-a" relationships.
PolymorphismOne interface, many behaviours — the same call resolves to different code per object.Write code against the base type; new types slot in for free.
▲ Gotcha

Abstraction vs encapsulation get confused constantly. Abstraction is design-level (hiding complexity behind a clean interface); encapsulation is implementation-level (hiding data behind access modifiers). One is about what to show, the other about how to protect it.

1.2 Polymorphism: static vs dynamic

"Many forms." It comes in two flavours, and naming which is which is a guaranteed interview question.

  • Compile-time / static polymorphism — resolved by the compiler using the method signature. This is method overloading (and, in C++, operator overloading).
  • Run-time / dynamic polymorphism — resolved at runtime using the actual object's type. This is method overriding, powered by dynamic dispatch.

Overloading (compiler decides)

java
class Calc {
    int    add(int a, int b)           { return a + b; }   // baseline
    double add(double a, double b)     { return a + b; }   // different param TYPES
    int    add(int a, int b, int c)    { return a + b + c; } // different param COUNT
}

// The compiler looks at the ARGUMENTS you pass and picks the matching method.
new Calc().add(2, 3);        // calls add(int,int)
new Calc().add(2.0, 3.0);    // calls add(double,double)

Overriding + dynamic dispatch (the object decides)

java
class Animal { void speak() { System.out.println("...generic..."); } }
class Dog extends Animal { @Override void speak() { System.out.println("Woof"); } }
class Cat extends Animal { @Override void speak() { System.out.println("Meow"); } }

Animal a = new Dog();   // reference TYPE = Animal, actual OBJECT = Dog
a.speak();              // prints "Woof" — chosen at RUNTIME from the real object

for (Animal x : new Animal[]{ new Dog(), new Cat() })
    x.speak();          // "Woof" then "Meow" — same call site, different code

Under the hood the JVM keeps a virtual method table (vtable) per class — a lookup of "for this method name, which actual implementation?" At the call a.speak() it follows the object's vtable, so the object's version runs regardless of the reference type.

◆ Key idea

In Java, instance methods are virtual by default — overriding "just works." In C++ you must opt in with virtual, otherwise the call binds statically to the reference type.

cpp
struct Animal { virtual void speak(); };   // 'virtual' enables dynamic dispatch
struct Dog : Animal { void speak() override; };

Animal* p = new Dog();
p->speak();   // WITH virtual -> Dog::speak().  WITHOUT virtual -> Animal::speak()

1.3 Overloading vs overriding

The single most asked OOP question. Memorize this table cold.

OverloadingOverriding
WhereSame class (or inherited)Across parent → child
SignatureSame name, different parametersIdentical name + parameters
BindingCompile-time (static)Run-time (dynamic)
PolymorphismStatic / ad-hocDynamic / subtype
Return typeCan differ (but type alone ≠ overload)Same, or a covariant subtype
Access modifierAnythingCannot be more restrictive
ExceptionsAnythingNo new/broader checked exceptions
static / final / privateAllowedCannot be overridden
▲ Gotcha

Return type alone does not overload. int f(int x) and double f(int x) won't compile together — the compiler can't tell them apart from the call site. You need a different parameter list.

◇ Interview lens
Q: "Can you overload the main method?"
A: Yes — main(String[]) is just a method; you can add main(int) etc. But the JVM only ever calls public static void main(String[] args) as the entry point. The overloads are normal methods you'd have to call yourself.
Q: "Is constructor overloading a thing?"
A: Yes — multiple constructors with different parameter lists. Constructors are never overridden (they aren't inherited), so only overloading applies.

1.4 Static members, hiding & binding

This is the "static inheritance" trap. Static members are inherited (a child can reference them), but they belong to the class, not the object — so they don't participate in dynamic dispatch. Redefining a static method in a child does not override it; it hides it.

java
class Parent {
    static void who() { System.out.println("Parent.who [static]"); }
           void hi()  { System.out.println("Parent.hi  [instance]"); }
}
class Child extends Parent {
    static void who() { System.out.println("Child.who [static]"); }   // HIDES
           void hi()  { System.out.println("Child.hi  [instance]"); } // OVERRIDES
}

Parent p = new Child();
p.who();   // "Parent.who" -> STATIC resolved by REFERENCE type (compile time)
p.hi();    // "Child.hi"    -> INSTANCE resolved by OBJECT type (runtime)

Same story for fields — they are resolved by the reference type, never polymorphic. This is field hiding:

java
class A { int x = 1; }
class B extends A { int x = 2; }   // hides A.x, doesn't override it

A ref = new B();
System.out.println(ref.x);          // 1  -> field picked by REFERENCE type A
System.out.println(((B) ref).x);    // 2  -> cast changes the reference type
◆ Key idea

One sentence to carry into the room: instance methods bind dynamically (object type); static methods and all fields bind statically (reference type). Override applies only to non-static, non-final, non-private instance methods.

1.5 Design patterns you must know

Patterns are reusable solutions to recurring design problems. They cluster into three families.

FamilySolvesCommon members
CreationalHow objects get madeSingleton, Factory Method, Abstract Factory, Builder, Prototype
StructuralHow objects composeAdapter, Decorator, Facade, Proxy, Composite
BehavioralHow objects talk & share responsibilityStrategy, Observer, Command, State, Iterator, Template Method

Singleton — exactly one instance

java
// (a) Bill Pugh holder — lazy, thread-safe, no locking. PREFERRED.
public class Config {
    private Config() {}
    private static class Holder { static final Config INSTANCE = new Config(); }
    public static Config get() { return Holder.INSTANCE; }   // class loaded only on first use
}

// (b) Double-checked locking — note 'volatile' (prevents seeing a half-built object)
public class Cache {
    private static volatile Cache instance;
    private Cache() {}
    public static Cache get() {
        if (instance == null) {                 // 1st check (no lock, fast path)
            synchronized (Cache.class) {
                if (instance == null)           // 2nd check (inside lock)
                    instance = new Cache();
            }
        }
        return instance;
    }
}

// (c) Enum — simplest; immune to reflection & serialization attacks
public enum Settings { INSTANCE; public void load() { /* ... */ } }
▲ Gotcha

Without volatile, double-checked locking is broken: instruction reordering can publish the reference before the constructor finishes, so another thread sees a non-null but half-initialized object.

Factory Method — ask for a type, get an object

java
interface Shape { double area(); }
class Circle implements Shape { public double area() { return 3.14159; } }
class Square implements Shape { public double area() { return 4.0; } }

class ShapeFactory {
    static Shape create(String type) {
        return switch (type) {
            case "circle" -> new Circle();
            case "square" -> new Square();
            default -> throw new IllegalArgumentException("Unknown: " + type);
        };
    }
}
Shape s = ShapeFactory.create("circle");   // caller never says 'new Circle()'

Builder — readable construction of complex objects

java
class Pizza {
    private final String size; private final boolean cheese, pepperoni;
    private Pizza(Builder b) { size = b.size; cheese = b.cheese; pepperoni = b.pepperoni; }

    static class Builder {
        private final String size;            // required
        private boolean cheese, pepperoni;    // optional
        Builder(String size)   { this.size = size; }
        Builder cheese()       { this.cheese = true;    return this; }   // fluent
        Builder pepperoni()    { this.pepperoni = true; return this; }
        Pizza build()          { return new Pizza(this); }
    }
}
Pizza p = new Pizza.Builder("large").cheese().pepperoni().build();

Builder kills the "telescoping constructor" anti-pattern (new Pizza("L", true, false, true, ...) where nobody can tell what the booleans mean).

Strategy — swap an algorithm at runtime

java
interface PayStrategy { void pay(int amount); }
class CardPay implements PayStrategy { public void pay(int a) { /* charge card */ } }
class UpiPay  implements PayStrategy { public void pay(int a) { /* charge UPI  */ } }

class Checkout {
    private PayStrategy strategy;
    void use(PayStrategy s)      { this.strategy = s; }   // inject behaviour
    void checkout(int amount)    { strategy.pay(amount); }
}
Checkout c = new Checkout();
c.use(new UpiPay());     // pick the algorithm without touching Checkout's code
c.checkout(500);

Observer — publish/subscribe

java
interface Observer { void update(String news); }

class NewsAgency {                                   // the Subject
    private final List<Observer> subs = new ArrayList<>();
    void subscribe(Observer o)   { subs.add(o); }
    void publish(String news)    { for (Observer o : subs) o.update(news); }
}
// Any number of observers register and get pushed updates — loose coupling.

Decorator — add behaviour by wrapping

java
interface Coffee { double cost(); }
class Plain implements Coffee { public double cost() { return 40; } }

abstract class AddOn implements Coffee {     // wraps another Coffee
    protected final Coffee inner;
    AddOn(Coffee c) { this.inner = c; }
}
class Milk extends AddOn {
    Milk(Coffee c) { super(c); }
    public double cost() { return inner.cost() + 10; }   // extend without subclass-explosion
}
Coffee order = new Milk(new Milk(new Plain()));   // 40 + 10 + 10 = 60
◇ Interview lens
Q: "Strategy vs State — aren't they the same code?"
A: Structurally near-identical, but intent differs. Strategy = the client picks an interchangeable algorithm. State = the object changes its own behaviour as its internal state transitions (and states often trigger the next state).
Q: "Why is Singleton sometimes called an anti-pattern?"
A: It's global mutable state in disguise — it hides dependencies, makes unit testing hard (can't easily mock/replace it), and can become a concurrency bottleneck. Dependency injection is usually cleaner.
// 02 · database management systems

DBMS: keeping data correct under chaos

A database's whole job is to stay correct while many users hammer it at once and machines crash mid-write. That promise is ACID, enforced by transactions, concurrency control, and recovery. Then we get hands-on with SQL.

2.1 Transactions & ACID

A transaction is a unit of work that must happen completely or not at all — classically, transferring money: debit one account, credit another. Halfway = corruption. Every transaction guarantees four properties:

PropertyPromiseEnforced by
A — AtomicityAll operations commit, or all roll back. No partial work.Undo log, COMMIT/ROLLBACK
C — ConsistencyThe DB moves from one valid state to another; constraints (keys, checks) always hold.Constraints + the app's correct logic
I — IsolationConcurrent transactions don't see each other's half-done work; result equals some serial order.Locks / MVCC
D — DurabilityOnce committed, data survives crashes and power loss.Write-ahead (redo) log on disk
sql
BEGIN;                                              -- start transaction
  UPDATE accounts SET balance = balance - 100 WHERE id = 1;   -- debit
  UPDATE accounts SET balance = balance + 100 WHERE id = 2;   -- credit
COMMIT;        -- both succeed together; if anything fails -> ROLLBACK (atomicity)

2.2 Concurrency control

Run transactions in parallel for speed, but interleaving creates anomalies. Interviewers expect you to name all four:

AnomalyWhat happens
Lost updateT1 and T2 both read X, both write; the later write silently erases the earlier one.
Dirty readT1 reads a value T2 wrote but hasn't committed; T2 then rolls back — T1 used data that never existed.
Non-repeatable readT1 reads a row, T2 updates+commits it, T1 reads again and gets a different value.
Phantom readT1 runs a range query, T2 inserts a new matching row, T1 re-runs and sees an extra row.

Isolation levels — the dial that trades safety for speed

LevelDirty readNon-repeatablePhantom
Read Uncommittedpossiblepossiblepossible
Read Committedpreventedpossiblepossible
Repeatable Readpreventedpreventedpossible*
Serializablepreventedpreventedprevented

*MySQL's InnoDB actually blocks phantoms at Repeatable Read too, using gap locks. Higher isolation = more correctness but more locking and less concurrency.

How isolation is implemented

  • Lock-based — shared (S) locks for reads, exclusive (X) locks for writes. The key protocol is Two-Phase Locking (2PL): a growing phase where a txn only acquires locks, then a shrinking phase where it only releases. 2PL guarantees conflict serializability.
  • Strict 2PL — hold all exclusive locks until COMMIT. Prevents cascading rollbacks (no one reads your uncommitted writes).
  • Timestamp ordering — each txn gets a timestamp; conflicting operations must execute in timestamp order, else the younger txn aborts.
  • MVCC (multi-version concurrency control) — every write creates a new version; readers see a consistent snapshot from their start time. Readers never block writers and vice-versa (used by PostgreSQL, InnoDB, Oracle).
◆ Key idea

A schedule is conflict-serializable if its precedence graph (node per txn, edge on each conflicting read/write pair) is acyclic. Acyclic ⇒ equivalent to some serial order ⇒ safe.

▲ Gotcha

Locking introduces deadlocks: T1 holds lock on A and wants B; T2 holds B and wants A. The DB detects the cycle in its wait-for graph (or hits a timeout) and aborts one transaction as the "victim."

2.3 Atomicity & recovery

How does "all-or-nothing" survive a power cut mid-write? The answer is Write-Ahead Logging (WAL): the change is written to a durable log before it touches the actual data pages.

  • Undo info lets the DB roll back uncommitted transactions after a crash (atomicity).
  • Redo info lets it re-apply committed transactions that hadn't reached disk (durability).
  • SAVEPOINT gives partial rollback inside a transaction.
sql
BEGIN;
  INSERT INTO orders(id, total) VALUES (1, 500);
  SAVEPOINT after_order;
  INSERT INTO items(order_id, sku) VALUES (1, 'BAD');
ROLLBACK TO after_order;      -- undo just the items insert, keep the order
COMMIT;
◇ Interview lens
Q: "How is atomicity actually achieved?"
A: The recovery manager + WAL. Every modification logs an undo (old value) and redo (new value) record before the data page is flushed. On restart, the DB replays the log: redo committed work, undo anything uncommitted. So a transaction is either fully reflected or fully erased.

2.4 Redundancy vs consistency

Store the same fact in two places and they will drift apart. Redundancy causes anomalies:

  • Update anomaly — change a department name, miss one row, now it's inconsistent.
  • Insertion anomaly — can't add a department until at least one employee exists in it.
  • Deletion anomaly — delete the last employee and you accidentally lose the department's info.

Normalization removes redundancy by splitting tables according to functional dependencies (X → Y means X determines Y).

FormRuleRemoves
1NFAtomic values only; no repeating groups / arrays in a cell.Multi-valued cells
2NF1NF + no partial dependency (every non-key column depends on the whole composite key).Partial dependencies
3NF2NF + no transitive dependency (non-key columns depend only on the key, not on other non-key columns).Transitive dependencies
BCNFFor every dependency X → Y, X must be a candidate key. (Stricter 3NF.)Remaining key anomalies
◆ Key idea

The 3NF mantra: "every non-key attribute depends on the key, the whole key, and nothing but the key." ("the key" = 1NF, "the whole key" = 2NF, "nothing but the key" = 3NF.)

Denormalization is the deliberate reverse: re-introduce redundancy (e.g. store dept_name on each order) to avoid expensive joins and speed up reads. The trade: faster reads, but slower/riskier writes and a duty to keep copies in sync. Normalize for OLTP correctness; denormalize selectively for read-heavy analytics.

2.5 SQL — from zero to interview queries

SQL has four sub-languages. Knowing which is which signals fluency:

DDL structure — CREATE, ALTER, DROP, TRUNCATE DML data — SELECT, INSERT, UPDATE, DELETE DCL access — GRANT, REVOKE TCL txns — COMMIT, ROLLBACK, SAVEPOINT

Our example schema (used throughout)

sql
CREATE TABLE departments (
    id   INT PRIMARY KEY,
    name VARCHAR(50) NOT NULL
);

CREATE TABLE employees (
    id         INT PRIMARY KEY,
    name       VARCHAR(50) NOT NULL,
    dept_id    INT,
    manager_id INT,                 -- self-reference to another employee's id
    salary     INT,
    FOREIGN KEY (dept_id)    REFERENCES departments(id),
    FOREIGN KEY (manager_id) REFERENCES employees(id)
);

SELECT — the workhorse

sql
SELECT name, salary          -- 1. which columns to return
FROM   employees             -- 2. from which table
WHERE  salary >= 50000        -- 3. keep only matching rows
ORDER  BY salary DESC         -- 4. sort the result
LIMIT  10;                    -- 5. cap the number of rows
◆ Key idea — logical execution order

SQL is written SELECT-first but executed in this order:

FROM/JOIN → WHERE → GROUP BY → HAVING → SELECT → DISTINCT → ORDER BY → LIMIT

That's why you can't use a SELECT alias in WHERE (WHERE runs before SELECT exists) but can use it in ORDER BY (which runs after).

JOINs — combining tables

A join matches rows across tables on a condition. The type decides what happens to non-matching rows.

JoinReturns
INNER JOINOnly rows with a match in both tables.
LEFT JOINAll left rows; right columns are NULL where no match.
RIGHT JOINAll right rows; left columns NULL where no match.
FULL OUTER JOINAll rows from both; NULLs on the side that's missing.
CROSS JOINCartesian product — every left row × every right row.
SELF JOINA table joined to itself (e.g. employee ↔ manager).
sql
-- INNER: employees that have a department
SELECT e.name, d.name AS dept
FROM   employees e
JOIN   departments d ON e.dept_id = d.id;

-- LEFT: ALL employees, dept shows NULL if they have none
SELECT e.name, d.name AS dept
FROM   employees e
LEFT JOIN departments d ON e.dept_id = d.id;

-- SELF JOIN: pair each employee with their manager
SELECT e.name AS employee, m.name AS manager
FROM   employees e
LEFT JOIN employees m ON e.manager_id = m.id;

Aggregation: GROUP BY & HAVING

Aggregate functions (COUNT, SUM, AVG, MIN, MAX) collapse many rows into one. GROUP BY makes one result row per group.

sql
SELECT dept_id,
       COUNT(*)    AS headcount,
       AVG(salary) AS avg_pay
FROM   employees
GROUP  BY dept_id
HAVING AVG(salary) > 60000      -- filter GROUPS (after aggregation)
ORDER  BY avg_pay DESC;
▲ Gotcha — WHERE vs HAVING

WHERE filters rows before grouping; HAVING filters groups after aggregation. You cannot put an aggregate in WHERE (WHERE COUNT(*) > 1 is illegal) — that's HAVING's job. Also: every non-aggregated column in SELECT must appear in GROUP BY.

Subqueries

sql
-- Scalar subquery: who earns above the company average?
SELECT name FROM employees
WHERE salary > (SELECT AVG(salary) FROM employees);

-- Correlated subquery: above-average WITHIN their own department
-- (inner query re-runs for each outer row, referencing e.dept_id)
SELECT e.name
FROM   employees e
WHERE  e.salary > (SELECT AVG(x.salary) FROM employees x WHERE x.dept_id = e.dept_id);

-- EXISTS: departments that have at least one employee
SELECT d.name FROM departments d
WHERE EXISTS (SELECT 1 FROM employees e WHERE e.dept_id = d.id);

Window functions (the senior-level differentiator)

Like aggregates, but they keep every row and compute across a "window" of related rows defined by OVER (PARTITION BY ... ORDER BY ...).

sql
SELECT name, dept_id, salary,
       ROW_NUMBER() OVER (PARTITION BY dept_id ORDER BY salary DESC) AS rn,
       RANK()       OVER (PARTITION BY dept_id ORDER BY salary DESC) AS rnk,
       DENSE_RANK() OVER (PARTITION BY dept_id ORDER BY salary DESC) AS drnk,
       SUM(salary)  OVER (PARTITION BY dept_id)                      AS dept_total,
       LAG(salary)  OVER (ORDER BY salary)                           AS prev_salary
FROM employees;
◆ Key idea — the three ranking functions on ties

For salaries 100, 100, 90: ROW_NUMBER = 1, 2, 3 (always unique). RANK = 1, 1, 3 (skips after a tie). DENSE_RANK = 1, 1, 2 (no gaps).

Classic interview queries

1 · Nth-highest salary (shown for 2nd) — three idiomatic ways:

sql
-- (a) Subquery — max salary that is below the overall max
SELECT MAX(salary) FROM employees
WHERE salary < (SELECT MAX(salary) FROM employees);

-- (b) DENSE_RANK — generalises to ANY N and handles ties cleanly
SELECT DISTINCT salary FROM (
    SELECT salary, DENSE_RANK() OVER (ORDER BY salary DESC) AS r
    FROM employees
) t
WHERE r = 2;                      -- change 2 to N

-- (c) LIMIT / OFFSET (MySQL, Postgres)
SELECT DISTINCT salary FROM employees
ORDER BY salary DESC
LIMIT 1 OFFSET 1;                 -- OFFSET = N - 1

2 · Find duplicates and 3 · earn more than manager:

sql
-- Duplicate names
SELECT name, COUNT(*) AS cnt
FROM   employees
GROUP  BY name
HAVING COUNT(*) > 1;

-- Employees who out-earn their manager (self-join)
SELECT e.name
FROM   employees e
JOIN   employees m ON e.manager_id = m.id
WHERE  e.salary > m.salary;

4 · Top earner per department and 5 · delete duplicates keeping one:

sql
-- Highest-paid employee in each department
SELECT name, dept_id, salary FROM (
    SELECT name, dept_id, salary,
           RANK() OVER (PARTITION BY dept_id ORDER BY salary DESC) AS r
    FROM employees
) t
WHERE r = 1;

-- Remove duplicate rows, keep the one with the smallest id
DELETE FROM employees
WHERE id NOT IN (
    SELECT MIN(id) FROM employees GROUP BY name, dept_id, salary
);

Indexes — why queries get fast (or slow)

An index is an auxiliary structure (usually a B+ tree) that keeps a column's values sorted with pointers to rows — turning a full-table scan O(n) into a tree lookup O(log n) for WHERE, JOIN, and ORDER BY.

sql
CREATE INDEX idx_emp_dept ON employees(dept_id);   -- speeds up filters/joins on dept_id
▲ Gotcha

Don't index everything. Each index costs storage and slows every INSERT/UPDATE/DELETE (the index must be maintained). Index high-selectivity columns used in filters and joins. A clustered index sorts the table itself (one per table); a non-clustered index is a separate structure pointing back to rows.

// 03 · operating systems

OS: sharing one machine, safely

An OS multiplexes finite hardware — CPU, RAM, devices — across many programs while keeping them isolated and responsive. The interview core: how concurrent work is coordinated (semaphores), how it deadlocks, and how virtual memory fakes infinite RAM.

3.1 Process vs thread

A process is a program in execution with its own address space and resources. A thread is the smallest unit of execution inside a process; multiple threads share the process's memory but each has its own stack, registers, and program counter.

ProcessThread
Address spacePrivate, isolatedShared with sibling threads
OwnsCode, heap, data, files, PCBStack, registers, PC, TCB
Creation / switchHeavy (new space, TLB flush)Light (same space)
CommunicationIPC: pipes, sockets, message queues, shared memoryDirect — shared variables
Fault isolationOne crash doesn't kill othersA bad thread can crash the whole process
Sync needed?Rarely (isolated)Constantly (shared data → races)
◆ Key idea

Threads buy concurrency cheaply by sharing memory — but that shared memory is exactly why you need locks/semaphores. Processes are safer but pricier to create and to communicate between.

3.2 Semaphores

A semaphore is an integer guarded by two atomic operations:

  • wait() (a.k.a. P / down): decrement; if the result would be negative, the caller blocks until a resource frees up.
  • signal() (a.k.a. V / up): increment; wake one waiting thread if any.

Binary semaphore

Value 0 or 1. Acts like a lock for mutual exclusion / signaling.

Counting semaphore

Any non-negative value. Controls access to a pool of N identical resources.

▲ Gotcha — mutex vs binary semaphore

They look alike but differ in ownership. A mutex has an owner: only the thread that locked it may unlock it (built for mutual exclusion). A semaphore has no owner: any thread can signal() it (built for signaling/counting). Using a semaphore where you need a mutex is a classic bug.

The canonical problem: producer–consumer

A bounded buffer of size N. Producers must wait if it's full; consumers must wait if it's empty; only one thread touches the buffer at a time. Three semaphores solve it:

c
semaphore mutex = 1;   // mutual exclusion on the buffer
semaphore empty = N;   // number of empty slots  (producers wait on this)
semaphore full  = 0;   // number of filled slots (consumers wait on this)

void producer() {
    while (true) {
        item = produce();
        wait(empty);          // is there room? block if buffer full
        wait(mutex);          // enter critical section
            buffer_put(item);
        signal(mutex);        // leave critical section
        signal(full);         // announce: one more item to consume
    }
}

void consumer() {
    while (true) {
        wait(full);           // is there an item? block if buffer empty
        wait(mutex);
            item = buffer_get();
        signal(mutex);
        signal(empty);        // announce: one more free slot
        consume(item);
    }
}
▲ Gotcha — lock ordering

Order matters: take the counting semaphore (empty/full) before mutex. Reverse it and you can deadlock — a producer holding mutex while blocked on a full buffer freezes every consumer that needs mutex to make room.

3.3 Deadlock

A deadlock is a set of processes each waiting forever for a resource another holds. It can happen only when all four Coffman conditions hold at once:

ConditionMeaningBreak it by…
Mutual exclusionA resource is non-shareable (one holder at a time).Make resources shareable where possible
Hold and waitA process holds resources while requesting more.Request all resources up front
No preemptionResources can't be forcibly taken; released only voluntarily.Allow preemption / rollback
Circular waitA cycle: P1 waits for P2's resource, … Pn waits for P1's.Impose a global ordering on resources

The four strategies

  • Prevention — structurally negate one of the four conditions (e.g. always acquire resources in a fixed numeric order ⇒ no circular wait).
  • Avoidance — grant a request only if the system stays in a safe state. The Banker's algorithm checks whether a safe sequence still exists (one ordering in which every process can get its max need and finish) before allocating.
  • Detection & recovery — allow deadlocks, find cycles in the wait-for graph, then recover by aborting a process or preempting resources.
  • Ignore (Ostrich algorithm) — pretend it won't happen; reboot if it does. What general-purpose OSes mostly do, because prevention is costly and deadlocks are rare.
◇ Interview lens
Q: "Difference between deadlock, starvation, and livelock?"
A: Deadlock — processes blocked forever in a cycle, none progresses. Starvation — a process is perpetually denied a resource (e.g. low priority always skipped) though others progress. Livelock — processes keep changing state in response to each other but make no real progress (two people stepping aside in a hallway forever).

3.4 Virtual memory

Virtual memory gives each process its own large, contiguous address space — even larger than physical RAM — by splitting memory into fixed-size pages (virtual) mapped to frames (physical) via a page table. The MMU translates every address; rarely-used pages live on disk.

Virtual address ──► [ Page table / TLB ] ──► Physical frame (page #, offset) translate page#→frame# (frame #, offset) If the page isn't in RAM ──► PAGE FAULT ──► OS loads it from disk (demand paging)
  • Demand paging — a page is loaded only when first accessed; the access traps as a page fault and the OS fetches it.
  • TLB (Translation Lookaside Buffer) — a small cache of recent page→frame translations, so most lookups skip the page table entirely.
  • Locality of reference — programs tend to reuse nearby addresses, which is why caching and paging work at all.

Page replacement — what to evict when RAM is full

AlgorithmEvicts…Note
FIFOThe oldest-loaded pageSimple, but suffers Belady's anomaly
Optimal (OPT)The page used farthest in the futureTheoretical best; needs the future, so unimplementable — a benchmark
LRUThe least-recently-used pageGood real-world choice; approximates OPT via the past
▲ Gotcha — Belady's anomaly

Intuitively, more frames ⇒ fewer faults. With FIFO this can reverse: adding frames sometimes increases page faults. LRU and OPT are "stack algorithms" and never show this anomaly.

◆ Key idea — thrashing

When the active working set doesn't fit in RAM, the system spends nearly all its time paging in/out instead of computing — that's thrashing. CPU utilization collapses. The fix: reduce the degree of multiprogramming (fewer processes) or add RAM so each working set fits.

// 04 · computer networks

Networks: getting bits across the world

Networking is layered so each level solves one problem and trusts the layer below. Nail the layer model and its protocols, the TCP vs UDP trade-off, how errors are caught/fixed, and how routers find paths.

4.1 The layers & their protocols

The OSI model has 7 conceptual layers; the practical TCP/IP model collapses them into 4. Data gains a header at each layer going down (encapsulation) and sheds it going up.

OSI layerJobProtocols / devicesUnit
7 ApplicationServices the user touchesHTTP(S), DNS, FTP, SMTP, DHCPData
6 PresentationEncoding, encryption, compressionTLS/SSL, JPEG, ASCIIData
5 SessionOpen / manage / close sessionsRPC, NetBIOSData
4 TransportEnd-to-end delivery, portsTCP, UDPSegment
3 NetworkLogical addressing & routingIP, ICMP · routersPacket
2 Data LinkNode-to-node, MAC, framingEthernet, ARP · switchesFrame
1 PhysicalRaw bits on the mediumCables, radio, signalsBit

TCP/IP mapping: Application (OSI 5–7) · Transport (4) · Internet (3) · Link/Network Access (1–2). Mnemonic, bottom-up: Please Do Not Throw Sausage Pizza Away.

▲ Gotcha — addresses live at different layers

MAC addresses are Layer 2 (physical, burned into the NIC, local hop). IP addresses are Layer 3 (logical, routable end-to-end). Port numbers are Layer 4 (which application/socket). ARP is what maps an IP to a MAC on the local network.

4.2 TCP vs UDP

Both are Layer-4 transport protocols; the choice is reliability vs speed. This comparison is asked in nearly every networking interview.

TCPUDP
ConnectionConnection-oriented (handshake first)Connectionless (just send)
ReliabilityGuaranteed, ordered, retransmits lost dataBest-effort — may drop, duplicate, reorder
Flow / congestion controlYes (windowing, slow start)No
Header size20+ bytes (heavier)8 bytes (lean)
SpeedSlower, more overheadFast, low latency
Use it forWeb, email, file transfer, anything that must arrive intactLive video/voice, gaming, DNS — where speed beats perfection
◆ Key idea — TCP 3-way handshake

To open a connection: SYN (client → server, "let's talk, seq=x") → SYN-ACK (server → client, "ok, seq=y, ack=x+1") → ACK (client → server, "ack=y+1"). Now both sides agree on starting sequence numbers. Teardown takes four steps (FIN/ACK each direction).

4.3 Error management

Bits flip in transit. The receiver must at least detect corruption, and sometimes correct it.

Detection — "is this frame damaged?"

SchemeHowCatches
Parity bitAdd 1 bit so the count of 1s is even (or odd).Any odd number of flipped bits
2D parityParity per row and per column.Detects + can correct a single-bit error
ChecksumSum the data words (one's-complement), send the sum; receiver re-adds.Many errors; weaker than CRC
CRCTreat data as a polynomial, divide by a generator, append the remainder.Very strong — esp. burst errors (used in Ethernet)

Correction — "fix it without re-asking"

Hamming code places parity bits at power-of-2 positions (1, 2, 4, 8…). Their combined "syndrome" pinpoints the exact position of a single flipped bit, so the receiver flips it back — single-error correction with no retransmission.

When you'd rather just ask again, you use ARQ (Automatic Repeat reQuest) — retransmission protocols built on ACKs and timeouts:

ARQ protocolBehaviourEfficiency
Stop-and-WaitSend one frame, wait for its ACK before sending the next.Low on high-latency links (line sits idle)
Go-Back-NSend a window of N frames; on a loss, retransmit that frame and everything after it. Receiver discards out-of-order frames.Better; wastes bandwidth re-sending good frames
Selective RepeatRetransmit only the lost frame; receiver buffers out-of-order ones.Best; needs buffering & more bookkeeping

4.4 Routing basics

Routing is how routers decide the path a packet takes across networks, using routing tables they build and update. Dynamic routing splits into two big families:

Distance Vector

Each router tells neighbors its distance to every destination; "routing by rumor." Computes paths with the Bellman-Ford algorithm.

Protocol: RIP (hop-count metric, max 15). Weakness: the count-to-infinity problem — slow to learn a link died. Mitigated by split horizon & poison reverse.

Link State

Each router floods the entire topology (link-state advertisements) to everyone, then independently runs Dijkstra's algorithm to find shortest paths.

Protocol: OSPF. Trade: faster convergence and loop-free, but more memory and CPU per router.

◆ Key idea

Within an organization you run an interior protocol (RIP / OSPF). Between organizations (autonomous systems), the internet runs BGP — a path-vector protocol that exchanges full AS-paths and applies policy, not just shortest distance.

◇ Interview lens
Q: "What actually happens when you type a URL and hit enter?"
A: Browser checks cache → DNS resolves the domain to an IP (Layer 7 over UDP) → TCP 3-way handshake to that IP:443 → TLS handshake for HTTPS → HTTP request sent → server responds → browser parses HTML, fetches assets, renders. Along the way IP routing (Layer 3) and ARP/MAC (Layer 2) move each packet hop by hop.
// 05 · system design

System Design: from one class to a million users

Two scales of the same skill. LLD is about classes, responsibilities and relationships inside one service — interviewers probe it through SOLID, UML and patterns. HLD is about boxes and arrows between services — load balancers, caches, databases, queues — and the trade-offs that hold them together. Learn the vocabulary, then the framework to apply it under pressure.

5.1 SOLID & Low-Level Design

SOLID is five principles for writing classes that are easy to change without breaking things. They are the backbone of every LLD interview — know the name, the one-line idea, and the smell each one fixes.

LetterPrincipleIn one line
SSingle ResponsibilityA class should have one reason to change — one job. A User class shouldn't also format reports and send email.
OOpen/ClosedOpen for extension, closed for modification. Add new behaviour by adding code, not editing tested code.
LLiskov SubstitutionA subclass must be usable anywhere its parent is, without surprises. The classic Square extends Rectangle trap breaks this.
IInterface SegregationMany small, focused interfaces beat one fat one. Don't force a class to implement methods it doesn't use.
DDependency InversionDepend on abstractions, not concretions. High-level code shouldn't new up low-level classes — inject interfaces.

The O and D principles are where most refactors happen. Here's the Open/Closed idea concretely — a payment processor that breaks the rule, then one that follows it:

JAVA
// ✗ Violates OCP: every new method forces editing this class
class PaymentService {
    void pay(String type) {
        if (type.equals("card"))      { /* ... */ }
        else if (type.equals("upi"))  { /* ... */ }   // edit again for each new type
    }
}

// ✓ Follows OCP + DIP: extend by adding a class, depend on the interface
interface PaymentMethod { void pay(double amount); }

class CardPayment implements PaymentMethod {
    public void pay(double amount) { /* charge card */ }
}
class UpiPayment implements PaymentMethod {
    public void pay(double amount) { /* charge UPI  */ }
}

class PaymentService {
    void checkout(PaymentMethod method, double amount) {
        method.pay(amount);          // new methods need ZERO changes here
    }
}
▲ Common mistake

Reciting "SOLID" but only being able to expand S. Interviewers always push: "give me an example of an LSP violation." Have one ready — the Square/Rectangle case (setting width on a Square silently changes height, breaking code written against Rectangle) or a Bird hierarchy where Penguin.fly() has to throw.

The other principles you'll be quizzed on

DRY — Don't Repeat Yourself KISS — Keep It Simple YAGNI — You Aren't Gonna Need It Composition > Inheritance

DRY says extract duplicated logic into one place. KISS and YAGNI push the other way — don't over-engineer or build for imaginary future needs. Composition over inheritance means prefer "has-a" (a Car has an Engine) over deep "is-a" trees, because inheritance is rigid and leaks parent details into children. The Strategy and Decorator patterns are composition in action.

Design patterns — the LLD recap map

You met several patterns in the OOP section. Interviewers expect you to place a pattern in its family and name when you'd reach for it. The three Gang-of-Four families:

FamilySolvesPatterns (★ = covered earlier)
CreationalHow objects get made★ Singleton, ★ Factory Method, ★ Builder, Abstract Factory, Prototype
StructuralHow objects are composed into bigger structures★ Decorator, Adapter, Facade, Proxy, Composite, Bridge
BehaviouralHow objects communicate & share responsibility★ Strategy, ★ Observer, Command, Template Method, State, Iterator, Chain of Responsibility
◇ Interview lens

Pattern questions are usually "which pattern fits X?" Quick reflexes: pluggable algorithms → Strategy; notify many on change → Observer; wrap to add behaviour → Decorator; make incompatible interfaces work together → Adapter; simplify a messy subsystem behind one entry point → Facade; build a complex object step-by-step → Builder; one shared instance → Singleton. Don't force patterns where a plain method works — that's an anti-pattern in itself.

5.2 UML relationships

LLD answers are drawn as class diagrams, so you must read and draw the relationship arrows. They differ by strength of coupling and lifetime ownership:

RelationshipMeaningLifetimeExample
Association
───>
"uses-a" / knows-about. A general link between two classes.IndependentA Teacher is associated with Students.
Aggregation
◇───
"has-a", weak ownership. The whole holds parts, but parts can outlive it.IndependentA Department has Professors — close the dept, profs still exist.
Composition
◆───
"owns-a", strong ownership. Parts can't exist without the whole.Tied togetherA House has Rooms — destroy the house, rooms go too.
Inheritance
──▷
"is-a". A subclass specialises a superclass.—Car is a Vehicle.
Dependency
- - ->
"depends-on" transiently — appears as a method parameter or local, not a field.MomentaryOrderService depends on a PaymentGateway passed to checkout().
◆ Key idea

Aggregation vs Composition trips everyone up. The test is lifetime: if destroying the container should destroy the parts, it's composition (filled diamond ◆). If the parts can live on independently, it's aggregation (hollow diamond ◇). Both are "has-a"; ownership strength is the difference.

A worked mini-LLD: a parking lot

This is the canonical LLD warm-up. You don't write full code — you identify entities, relationships and key behaviours. A clean first pass:

DESIGN
ParkingLot  ──◆── Floor          // lot OWNS floors (composition)
Floor       ──◆── ParkingSpot    // floor OWNS spots
ParkingSpot ──>   Vehicle        // spot references the parked vehicle

Vehicle (abstract)               // is-a hierarchy
  ├── Car        extends Vehicle
  ├── Bike       extends Vehicle
  └── Truck      extends Vehicle

SpotType  enum { COMPACT, LARGE, BIKE, EV }    // strategy for fit-check
Ticket    { id, spot, entryTime, exitTime }
FeeStrategy (interface)          // ✦ Strategy pattern: hourly / flat / EV
ParkingLot.assignSpot(Vehicle)   // key behaviour → returns Ticket
ParkingLot.processExit(Ticket)   // → FeeStrategy.calculate()

Notice the SOLID/pattern hooks: FeeStrategy keeps pricing open/closed, the Vehicle hierarchy uses inheritance, and the lot→floor→spot chain is composition. Calling those out by name is exactly what scores points.

5.3 HLD building blocks

High-Level Design zooms out to services, data stores and the network between them. There's a fixed toolbox of components; an HLD answer is choosing the right ones and justifying the trade-offs. Start with the most fundamental choice — how you grow.

Scaling: vertical vs horizontal

Vertical (scale up)

Buy a bigger machine — more CPU/RAM. Simple, no code changes. But there's a hard ceiling, it's expensive, and that one box is a single point of failure.

Horizontal (scale out)

Add more machines and split the load. Near-unlimited and fault-tolerant, but needs a load balancer, and your app must be stateless (or share state externally) to spread freely.

Load balancing

A load balancer sits in front of your servers and distributes incoming requests so no single server is overwhelmed. It also does health checks — routing traffic away from dead instances. Common algorithms: round-robin (rotate evenly), least-connections (send to the least-busy server), and consistent hashing (stick a given user/key to the same server, vital for caches).

Caching

A cache stores hot data in fast memory (e.g. Redis, Memcached) so you avoid slow recomputes or DB hits. Caching is the single highest-leverage performance lever — and the source of the hardest bugs (stale data, invalidation).

ConceptWhat it is
Cache-aside (lazy)App checks cache first; on a miss, reads DB and populates the cache. Most common.
Write-throughWrites go to cache and DB together — cache always fresh, writes slightly slower.
Write-backWrite to cache now, flush to DB later — fast, but risks data loss on crash.
Eviction policyWhen full, what to drop: LRU (least recently used) is the default; also LFU, FIFO.
TTLTime-to-live — entries auto-expire to bound staleness.
CDNContent Delivery Network — caches static assets (images, JS, CSS) at edge servers physically near users.
▲ Common mistake

Forgetting cache invalidation. "There are only two hard things in CS: cache invalidation and naming things." When the underlying data changes, the cached copy is now stale. Always state your invalidation strategy (TTL expiry, or explicit delete-on-write) — interviewers wait for it.

Database scaling: replication & sharding

Replication

Copy data across multiple DB nodes. Primary-replica: writes go to the primary, reads spread across read-replicas — great for read-heavy loads. Adds replication lag (replicas briefly behind).

Sharding (partitioning)

Split one big dataset across DBs by a shard key (e.g. user_id % N, or by region). Each shard holds a slice. Scales writes too, but cross-shard queries and joins get painful, and a bad key causes hot shards.

Partitioning comes in two flavours: horizontal (split rows across stores — what "sharding" usually means) and vertical (split columns/tables by access pattern).

SQL vs NoSQL

SQL (relational)NoSQL
SchemaFixed, predefinedFlexible / schema-less
ScalingVertical mainly; sharding is manualBuilt for horizontal scale-out
GuaranteesStrong ACID, joins, complex queriesOften BASE; eventual consistency, denormalized
Use whenRelationships, transactions, reporting (banking, orders)Massive scale, flexible/changing data (feeds, logs, catalogs)
TypesPostgres, MySQLDocument (Mongo), key-value (Redis/DynamoDB), wide-column (Cassandra), graph (Neo4j)

CAP theorem

The cornerstone trade-off of distributed data. A distributed store can guarantee only two of these three at once:

C — Consistency (every read sees the latest write) A — Availability (every request gets a response) P — Partition tolerance (works despite network splits)

Since network partitions will happen at scale, P is non-negotiable — so the real choice is C vs A. A CP system (e.g. a banking ledger) refuses to answer rather than return stale data; an AP system (e.g. a social feed) always answers, accepting it may be briefly out of date.

◇ Interview lens

The sharper modern framing is PACELC: if Partitioned, choose A or C; Else (normal operation) choose Latency or Consistency. It captures that even with no partition, you still trade speed against strong consistency. Naming PACELC signals depth beyond the textbook CAP triangle.

Async & the rest of the toolbox

ComponentWhy it exists
Message queue (Kafka, RabbitMQ, SQS)Decouples producer from consumer. Smooths traffic spikes, enables async work (email, video encoding), and buffers so a slow consumer doesn't drop requests.
API GatewaySingle entry point for clients — handles routing, auth, rate limiting, and request aggregation in front of many microservices.
Rate limiterCaps requests per client (e.g. token-bucket / leaky-bucket algorithm) to prevent abuse and protect downstream services.
Reverse proxyFronts servers for SSL termination, compression, and caching (e.g. Nginx).
Blob/object store (S3)Stores large files (images, video, backups) cheaply — DBs are the wrong place for these.

Microservices vs monolith

Monolith

One deployable unit. Simple to build, test and deploy early; fast in-process calls. But it gets tangled at scale and you must redeploy everything for any change.

Microservices

Many small services, deployed independently, each owning its data. Scale and ship teams independently — at the cost of network latency, distributed-system complexity, and harder debugging. Start monolith, split when pain is real.

◆ Key idea — back-of-the-envelope estimation

Interviewers want rough numbers, not precision. Anchor on a few constants: 1 day ≈ 86,400 s ≈ 10⁵ s; a read from memory ≈ 100 ns, from SSD ≈ 100 µs, from disk/network ≈ 1–100 ms. Method: users → requests/day → divide by 10⁵ for QPS → ×payload for bandwidth → ×retention for storage. Example: 100M daily users × 10 requests = 10⁹/day ÷ 10⁵ ≈ 10k QPS average (×2–3 for peak). Always separate read QPS from write QPS — it drives your whole design.

5.4 The interview framework

A system-design round is open-ended on purpose — they're watching your process. Don't jump to drawing databases. Follow a script so you never freeze. The standard 6-step flow:

  1. Clarify requirements (~5 min). Pin down functional needs ("users post and follow") and non-functional ones (scale, latency, consistency vs availability, read:write ratio). Never design before scoping — ask first.
  2. Estimate scale. Back-of-envelope the QPS, storage, and bandwidth from the numbers above. This justifies every later choice (do we even need sharding?).
  3. Define the API. List the core endpoints — createPost(), getFeed(userId). This nails down exactly what the system must do.
  4. Design the data model. Pick SQL vs NoSQL with a reason, sketch the main tables/entities and the access patterns that shape them.
  5. Draw the high-level design. Client → load balancer → app servers → cache → DB, plus queues/CDN/blob store as needed. Walk a request through end to end.
  6. Deep-dive & address bottlenecks. Pick the hard part (the feed fan-out, the hot shard) and go deep. Name the single points of failure and how you'd remove them (replication, multiple LBs), then the trade-offs you accepted.
◇ Interview lens

What actually gets you the offer: thinking out loud (they're hiring your reasoning), stating trade-offs explicitly ("I'll use cache-aside, accepting brief staleness for big latency wins"), and driving the conversation rather than waiting to be asked. There's no single correct architecture — a well-justified design always beats a "perfect" one you can't explain.