My interview-prep and engineering notes, cleaned up and organised by topic. Pick a page on the left,
read, and ask a question at the bottom if something is unclear.
πΊοΈ Roadmap
The map I used to prepare for backend / SDE-2 interviews. Everything on this site hangs off these buckets.
Read & scope (5β10 min): clarify requirements, list the must-have flows, park the nice-to-haves.
Model: list entities β relationships (association / aggregation / composition) β enums for fixed states.
Structure: packages by layer (model, service, repository, strategy), main() to demo.
Patterns & SOLID: talk about them (Factory, Builder, Strategy, interfaces at variation points) β if time is short, don't implement them; implement the main functionality correctly first.
Correctness: input validation, custom exceptions, thread-safety where state is shared (ConcurrentHashMap, synchronized, atomics).
Finish strong: working demo > extra features; a few unit tests on the core flow; walk through the design and the trade-offs you took.
Cache β 80/20 rule: cache 20% of daily read volume.
Example β URL shortener
100M DAU Γ 1 write/day = 100M writes/day β ~1K write QPS
reads 10Γ writes = 1B reads/day β ~10K read QPS
each record β 500 B Γ 100M Γ 365 Γ 5y β 90 TB over 5 years
Distributed Systems Concepts
Vocabulary that comes up in every HLD discussion β with the precise meaning, not the hand-wavy one.
Idempotency
An operation is idempotent if executing it multiple times with the same input produces the same result as executing it once. PUT is idempotent; PATCH and POST generally are not. Pair idempotent APIs with retry + exponential backoff. Store idempotency keys durably β Redis can go down, so keep a DB fallback.
Consistency
Eventual consistency β "quick response now, consistent eventually". Fine when real-time accuracy isn't critical (DynamoDB, WhatsApp profile picture propagating across devices).
Dirty read β reading data another transaction modified but hasn't committed.
Skewed / stale read β write to node A, read from node B before replication catches up. A side effect of eventual consistency.
Materialized view β a DB object storing precomputed query results.
Race conditions
A timing bug between concurrent operations: multiple threads/processes touch shared data, at least one writes, and there's no synchronisation. Classic: two users buy the last item at once β overselling.
Locks / mutexes around shared resources
Atomic operations (AtomicInteger, DB atomic updates)
Thread-safe data structures Β· DB transactions (ACID) Β· synchronized
Failover Β· Fallback Β· Failback
Term
Meaning
Fallback
"If something goes wrong, do this instead." Core of fault tolerance along with circuit breakers, timeouts, retries.
Failover
Automatic switch from primary to backup during a failure.
Failback
Returning from backup to the original primary once it recovers. Hard parts: data consistency, disruption during switch, primary failing again.
Safe failback needs: health checks, data sync, LB switching, automation, quorum checks, graceful shutdown, shadow testing, and (for critical systems) manual approval.
Coupling
Aspect
Tight
Loose
None
Know each other
Yes
No
No
Share contracts
Yes
Yes (topics/schema)
No
Direct calls
Yes
No
No
Change impact
High
Medium
None
Realistic?
Yes
Yes
β No
Pub/Sub has loose coupling, not no coupling β publishers and subscribers still agree on topic name, message schema, semantics, and broker.
Other terms worth owning
Transition state β a short-lived intermediate state ("Authenticating", "Processing Payment") between stable states.
Dedupe β keep one unique copy, link the rest. Saves storage, cleans lists, stops fraud.
Sidecar β helper process/container attached to the main app for auxiliary tasks (proxying, logging, auth).
Upstream / downstream β services you call vs services that call you.
Fan-out / fan-in β one event to many consumers / many results merged into one.
Complacency trap β assuming you know everything so you stop asking questions. Dangerous in design reviews.
Kafka
Distributed messaging / event-streaming platform. Logs, sensor data, events β stock market feeds, social feeds, IoT. High throughput and horizontally scalable.
Core vocabulary
Producer / Consumer
Topic β logical category/feed, like a DB table.
Broker β a server; a cluster is multiple brokers.
Partition β unit of parallelism. Ordering is guaranteed only within a partition.
Offset β sequential id of a message inside a partition.
Replication factor β avoid single point of failure.
Consumer group β each partition consumed by exactly one consumer in the group (load balancing + fault tolerance). Each group keeps its own offsets, so multiple apps can consume the same stream independently (broadcast).
Why is Kafka fast if it writes to disk?
Sequential, append-only writes β linear disk writes can beat random memory access.
Zero-copy transfer β bypasses the user-space copy between disk and socket.
OS page cache instead of an in-process cache β no JVM GC penalty, cache stays warm across restarts.
Producer parallelism β keyed messages hash to a partition; unkeyed are round-robined.
Producer acks
Setting
Behaviour
Risk
acks=0
Fire-and-forget
Data loss, fastest
acks=1
Leader acknowledges
Lost if leader dies before followers replicate
acks=all / -1
All in-sync replicas acknowledge
Safe only with min.insync.replicas β₯ 2
Operational things interviewers probe
Rebalance is "stop-the-world" β consumption pauses while partitions are reassigned. Use static membership in volatile environments (K8s spot instances) to avoid full rebalances.
Consumer lag β delay between produce and successful consume; the key health metric.
Restart semantics β a consumer resumes from its group's last committed offset; a dead pod's partitions are reassigned to surviving members of the group.
Pair Kafka with the Outbox pattern for exactly-once-ish DB + event publishing.
Redis & Caching
Redis is more than a cache: cache, Pub/Sub, persistence (snapshot or append-only file), locks (Redisson), rate limiting.
Caching basics
Hit / miss, TTLs, cache-aside vs write-through vs write-behind.
Eviction: LRU, LFU, FIFO β be ready to argue why one suits your system.
Cache ~20% of hot data (80/20 rule) for most read-heavy systems.
Caching a balance or anything money-related? Treat the DB as source of truth; cache only derived/read views.
Redis vs Memcached
Memcached is a pure in-memory key-value cache. Redis adds data structures (lists, sets, sorted sets, hashes), persistence, Pub/Sub, Lua scripting and clustering.
Redis Cluster β slot-to-node metadata cache
Keys hash (CRC16) into 16,384 slots spread over nodes. The client caches the slotβnode topology so each command is one network trip instead of "ask where key lives, then send command".
Without metadata cache: App β ask cluster where key lives β answer β send command (2 trips)
With metadata cache: App β local topology β send command directly (1 trip)
Why not implement that map in your own service? You'd also need slot calculation, cluster discovery (CLUSTER SLOTS), MOVED handling, rebalancing, retries, failover, connection management and health checks. That's why Jedis / Lettuce / Redisson exist.
Idempotency store
Redis can go down. If you store idempotency records only in Redis you can double-process. Keep a durable record in the database and use Redis as the fast path.
CDN
Geographically distributed cache for static (and increasingly dynamic) content β video chunks (~4 s MP4 segments with a manifest listing bitrates for ABR/HLS/DASH), images, JS/CSS.
Load Balancers & Proxies
A proxy is an intermediary acting on behalf of another entity. A load balancer is a reverse proxy that distributes load across instances of the same service.
IP Hashing β sticky sessions; stateful requests become easy
Network bandwidthβaware (e.g. video streaming)
Good LBs also watch %CPU, RAM and in-flight requests via health checks / heartbeats to make better decisions. Keep health-check polling short.
Consistent hashing
Place servers and keys on a hash ring; a key goes to the next server clockwise. Adding/removing a server moves only the keys in one arc, not everything. Use virtual nodes to spread load evenly. Questions to be able to answer: how data migrates when a shard is added/removed, who manages it (app layer vs DB layer), and how the app connects to shards + replicas (e.g. MongoDB mongos router, PostgreSQL read replicas).
Databases & Storage
What to know, grouped the way interviewers ask about it.
Order placement, portfolio feed, notification system, payment processing, real-time price feed. Remember: consistency > availability, and go deep on two components β usually schema + caching.
How to approach an LLD problem
Use this as your script. Timings are for a 45β60 min round. Always narrate why you choose an approach.
Clarify requirements & scope (1β2 min) β core operations, scale, performance goals, persistence, correctness vs eventual, concurrency needs. Decide constraints: single node vs distributed, QPS, memory.
State APIs / use-cases (1β3 min) β list main APIs with example calls and input/output shapes.
High-level components (2β5 min) β draw boxes for main classes/components and their interactions. Simple first.
Detailed class design (5β12 min) β classes, methods, key fields, relationships (aggregation/inheritance). Highlight interfaces/abstractions (SOLID). Show the sequence for main flows.
Data structures & algorithms (3β8 min) β pick DS for critical ops (priority queue, hashmap, LRU list) and state complexity.
Extensions & trade-offs (1β3 min) β what you'd add next and what you consciously gave up.
Practice set
Hotel Management Β· Car Parking Β· Elevator Β· Chess Β· Ticket Booking Β· ATM Β· Tic-Tac-Toe. Resources: codezym.com, "System Design and Gossip" on YouTube.
Moving from definitions to instinct
Practise scenario-based quizzes, "class vs interface vs abstract class?" questions, pattern identification, and refactoring exercises. Group similar patterns (Factory/Abstract Factory, Decorator/Proxy, Strategy/State) and for each ask: what's the problem, which pattern solves it, how does it look in real code?
SOLID Principles
Core intention: control change. SOLID is not about splitting everything β it's about finding volatility points (where change happens) and applying principles there.
S β Single Responsibility
"A class should have only one reason to change." Think Swiss Army knife: sharpen the blade and you carry the whole tool. Granularity: class level (sometimes method).
// β three reasons to change: business rule, email provider, DB
class UserService {
void registerUser(User u) {}
void sendEmail(User u) {}
void saveToDB(User u) {}
}
// β SRP applied
class UserService { void registerUser(User u) {} }
class EmailService { void sendEmail(User u) {} }
class UserRepository{ void save(User u) {} }
If a class has multiple reasons to change, split it.
O β Open/Closed
"Open for extension, closed for modification." A game doesn't change when a new controller ships β the controller implements the standard interface. Apply where behaviour varies (payments, notifications, storage types).
// β every new payment type modifies this
if (type.equals("CARD")) ... else if (type.equals("UPI")) ...
// β
interface Payment { void pay(); }
class CardPayment implements Payment {}
class UpiPayment implements Payment {}
L β Liskov Substitution
"Subtypes must be substitutable for their base types." A Smart TV remote must still have a working power button. Square/Rectangle with setWidth/setHeight is the classic violation. Applies only where inheritance exists.
// β
class Bird { void fly() {} }
class Ostrich extends Bird { void fly() { throw new UnsupportedOperationException(); } }
// β fix the abstraction
interface Bird {}
interface FlyingBird extends Bird { void fly(); }
class Sparrow implements FlyingBird {}
class Ostrich implements Bird {}
If a child cannot honour parent behaviour β wrong abstraction.
I β Interface Segregation
"Clients shouldn't depend on interfaces they don't use." Separate breakfast / lunch / drinks menus instead of one giant page.
"High-level modules should not depend on low-level modules; both depend on abstractions." The lamp has a plug, the wall has a socket β neither cares about the other's wiring. Bonus: interfaces are mockable, concrete classes aren't. Apply at system boundaries: DB, APIs, messaging, storage.
// β
class OrderService { MySQLDB db = new MySQLDB(); }
// β inject the abstraction
class OrderService {
private final DBInterface db;
OrderService(DBInterface db) { this.db = db; }
}
Where each applies
Principle
Granularity
Apply at
SRP
Class / method
Any class with >1 reason to change
OCP
Behaviour variation point
Places new types/behaviours are expected
LSP
Inheritance hierarchy
Wherever inheritance exists
ISP
Interface design
Implementers that use only parts
DIP
Module interaction
High-level logic β low-level details
Design Patterns Cheat Sheet
When to use Β· what to use Β· how it works. Revise by grouping similar patterns and coding two a day.
Creational
Pattern
When
What
How
Singleton
Only one instance should exist (Logger, Config)
Private constructor + static instance
Restrict instantiation, give global access. Watch out for double-checked locking, reflection, serialization.
Factory
Creation logic varies by input
Central factory method
switch/map returns the right subclass
Abstract Factory
Families of related objects (Mac vs Windows UI)
Factory of factories
Each factory creates a related family
Builder
Many optional fields
Chained step-by-step construction
Separate construction from representation; supports immutability
Prototype
Cloning is cheaper than creating
Clone an existing object
clone() or copy constructors
Structural
Pattern
When
What
How
Adapter
Interfaces are incompatible
Wrapper
Converts one interface into what the client expects
Bridge
Decouple abstraction from implementation
Two independent hierarchies
Abstraction holds a reference to the implementation
Composite
Treat individual & group uniformly
Tree with common interface
Recursive composition
Decorator
Add behaviour dynamically
Wrap in layers
Composition decorates the original
Facade
Simplify a complex subsystem
One high-level interface
Encapsulates multiple subsystems
Proxy
Control access (caching, lazy loading, security)
Stand-in object
Same interface, controls access
Flyweight
Many similar objects, memory matters
Share intrinsic state
Factory + cache of shared instances
Behavioural
Pattern
When
What
How
Strategy
Multiple algorithms selectable at runtime
Encapsulated algorithms
Interface + interchangeable implementations
Observer
One-to-many updates (UI, events)
PublisherβSubscriber
Subject keeps a list of observers
Command
Request as an object (undo/redo)
execute() interface
Client creates, invoker runs, receiver executes
Chain of Responsibility
Pass request along handlers
Linked handlers
Each handles or forwards
State
Behaviour changes with internal state
State subclasses
Context delegates to the current state
Template Method
Skeleton in base, steps in subclasses
Abstract class with hooks
Some methods fixed, others overridden
Iterator
Traverse without exposing structure
Iterator interface
Separate traversal object
How to revise
Group similar patterns: Factory & Abstract Factory Β· Decorator & Proxy Β· Strategy & State.
For each ask: what is the problem? which pattern solves it? how does it apply in real code?
Code 2 patterns a day, even with print statements.
Real-world anchors help: Google Maps β Flyweight + Proxy.
OOP Relationships
Association vs Aggregation vs Composition β and the four questions that settle it every time.
The decision ladder
Do the objects relate at all? If they know about / use each other β at least an Association. (Teacher teaches Student.)
Is one a whole and the other a part? No β stay with Association. Yes β go deeper.
Who owns the lifecycle? Part dies with the whole β Composition (Human β Heart). Part lives independently β Aggregation (Library β Books).
Association vs Aggregation β both allow independent lifecycles. Ask: is one logically "part of" the other? Yes β Aggregation (ClassroomβStudents). No, just a link β Association (TeacherβStudent).
Mental shortcut
If "X has Y" sounds natural β wholeβpart (Aggregation / Composition). If only "X works with Y" sounds natural β Association.
Relationship
Wholeβpart?
Lifecycle
Example
Association
No β link only
Independent
Teacher β Student, Bank employs Employees
Aggregation
Yes
Independent (weak ownership)
Library β Books, Classroom β Students
Composition
Yes
Dependent (strong ownership)
Human β Heart, House β Rooms
Related vocabulary
Upcasting β superclass reference pointing at a subclass object.
Loose coupling β code written against an interface; swap the implementation without touching callers.
High cohesion β interfaces define clear, focused responsibilities.
Machine Coding Round
90β120 minutes, working code, clean structure. Here's the order of operations and the shortcuts that save time.
Order of operations
List all entities β see how they link β implement only the basic requirement set first.
Keep code clean while creating objects β Factory for entity creation, Builder when there are many optional fields.
No database, no UUID complexity. A static counter inside the class is enough.
public class User {
private static int counter = 0; // class-level counter
private final String id;
private final String name;
public User(String name) {
this.id = "USR-" + (++counter); // USR-1, USR-2, USR-3
this.name = name;
}
}
Checklist before you say "done"
Main flow demonstrable from a main() or test.
Interfaces for anything that could vary (pricing, notification, storage).
Thread-safety called out where shared state exists (even if not implemented).
Edge cases listed verbally: invalid input, duplicates, empty states.
C++ STL β things to care of
The traps that bite in interviews and production, plus the STL β Java mapping for when you switch languages.
Things to care of
priority_queue is a max-heap by default. Min-heap: priority_queue<int, vector<int>, greater<int>>. (Java's PriorityQueue is the opposite β min-heap by default.)
Iterator invalidation.vector::push_back may reallocate and invalidate all iterators/pointers/references; erase invalidates from the erased point onward. unordered_map rehash invalidates iterators (not references). map/set/list only invalidate the erased element.
Comparators must be a strict weak ordering. Return true only when a is strictly before b. Using <= is undefined behaviour in sort and can crash.
operator[] on a map inserts.m[k] default-constructs a value if missing. Use find / count / at for read-only lookups.
unordered_map worst case is O(n). Adversarial inputs can trigger collisions β in contests use a custom hash or fall back to map.
lower_bound / upper_bound need sorted ranges. On set/map use the member versions (O(log n)), not std::lower_bound (O(n) on non-random-access iterators).
reserve() when you know the size β avoids repeated reallocation. emplace_back constructs in place; push_back copies/moves.
Erase-remove idiom:v.erase(remove_if(v.begin(), v.end(), pred), v.end()); β or std::erase_if(v, pred) in C++20.
size() is unsigned.v.size() - 1 on an empty vector wraps around. Compare with int carefully.
Prefer std::array / vector over raw arrays; prefer string_view for read-only string params.
Pass large containers by const&; return by value (RVO / move semantics make it cheap).
std::deque is the right default for queues/stacks (stack and queue are adaptors over it).
Complexity reminders
Container
Lookup
Insert / erase
Ordered?
vector
O(1) by index
O(1) amortised at end, O(n) middle
Insertion order
deque
O(1)
O(1) both ends
Insertion order
list
O(n)
O(1) with iterator
Insertion order
map / set
O(log n)
O(log n)
Sorted (red-black tree)
unordered_map / set
O(1) avg, O(n) worst
O(1) avg
No
priority_queue
O(1) top
O(log n)
Heap order
C++ STL β Java mapping
C++ STL
Java
Notes
vector<T>
ArrayList<T>
O(1) get, O(n) middle insert
list<T>
LinkedList<T>
Doubly linked
map<K,V>
TreeMap<K,V>
Sorted, O(log n)
unordered_map<K,V>
HashMap<K,V>
Default choice
set<T>
TreeSet<T>
Sorted
unordered_set<T>
HashSet<T>
Default choice
priority_queue<T>
PriorityQueue<T>
Min-heap by default in Java
deque / stack / queue
ArrayDeque<T>
Don't use legacy Stack
pair<A,B>
record / Map.Entry
No built-in Pair
multimap<K,V>
Map<K, List<V>>
Use computeIfAbsent
Java Essentials
The idioms and gotchas that matter most when you're writing Java under interview pressure.
Golden rule: declare with the interface type
List<String> names = new ArrayList<>(); // β
Map<String, Integer> freq = new HashMap<>();
Set<String> seen = new HashSet<>();
ArrayList<String> names = new ArrayList<>(); // β too specific
Swap ArrayList for LinkedList later by changing one word, not every signature.
HashMap β the patterns you'll use most
map.get("a"); // returns null if absent β NOT an exception
map.getOrDefault("a", 0); // safe get
map.merge("a", 1, Integer::sum); // frequency count
map.computeIfAbsent("a", k -> new ArrayList<>()).add("item"); // multimap
for (Map.Entry<String,Integer> e : map.entrySet()) { e.getKey(); e.getValue(); }
map.get() returns null for missing keys. Always use getOrDefault() unless you're explicitly checking for null.
No built-in Pair β use a record
record Point(int x, int y) {}
public record Pair<L, R>(L left, R right) {}
Point p = new Point(3, 5); p.x(); p.y(); // accessors, no 'get'
Map<Point, String> grid = new HashMap<>(); // equals/hashCode auto-generated
Records are immutable, so they're safe as map keys and in sets.
Sorting
Collections.sort(nums); // ascending
nums.sort(Comparator.reverseOrder()); // descending
Arrays.sort(arr); // int[] ascending (no reverse for primitives)
cars.sort(Comparator.comparingInt(Car::price).thenComparing(Car::name));
cars.sort((a, b) -> b.speed - a.speed); // lambda, descending by speed
Primitive vs reference fields
Primitive (int, booleanβ¦) stores the value directly, fixed size, never null, no methods.
Reference (any class, interface, enum, array, String) stores an address into the heap; can be null; has methods.
Assigning a primitive copies the value; assigning a reference copies the address β both now point to the same object.
Java is strictly pass-by-value
What's passed by value for a reference type is the reference. So a method can mutate the object you passed, but reassigning the parameter inside the method doesn't affect the caller.
static void modify(List<String> list) {
list.add("Orange"); // β visible to caller β same object
list = new ArrayList<>(); // β only rebinds the local copy of the reference
list.add("New Item");
}
The one mental model: a thread is needed to execute CPU work, but it does not need to stay occupied while waiting for non-blocking I/O.
Blocking vs non-blocking I/O
Blocking: Thread β send request β WAIT β¦β¦β¦β¦β¦β¦ response β continue
(thread occupied the whole time)
Non-blocking: Event loop β start A β start B β process C β start D
β later: A's response arrives β process A
(no thread sits waiting on A)
Spring Boot (MVC) uses a multi-threaded thread pool β one thread per in-flight request. Spring WebFlux on Netty uses an event loop; the controller returns Mono/Flux and Spring subscribes as part of handling the request.
Singleton beans and thread safety
Spring creates one instance of a @Service and reuses it. Threads 1β¦N all call the same object.
@Service
public class PaymentService {
public Payment getPayment(String id) { // local variables only β stateless β thread-safe
return fetchPayment(id);
}
}
Stateless is one way to be thread-safe. But thread-safe β stateless β you can hold state if it's protected (concurrent collections, atomics, locks).
Toolbox
Need
Reach for
Counter / flag
AtomicInteger, AtomicLong, AtomicBoolean
Shared map
ConcurrentHashMap + computeIfAbsent
Critical section
synchronized, ReentrantLock (tryLock, fairness)
Readers β« writers
ReadWriteLock
Producer / consumer
BlockingQueue
Thread pool
ExecutorService, CompletableFuture
Distributed lock
Redisson (Redis), DB row lock / SELECT β¦ FOR UPDATE SKIP LOCKED
Singleton pitfalls (LLD favourite)
Lazy initialisation needs double-checked locking with a volatile field β or just use an enum / static holder.
Reflection and serialization can create a second instance; guard the constructor and implement readResolve().
Database locking recap
Databases use shared locks for reads and exclusive locks for writes. Many shared locks coexist; an exclusive lock blocks both. Deadlocks are detected with wait-for graphs β on a cycle, one transaction is chosen as victim and rolled back.
Ask
π¬ Have a question about any of this?
Pick the topic, type your question, and hit send β it opens an email to me pre-filled with your
question so I can reply directly. If it's a good one I'll add the answer to that page.
Can I suggest a correction or a new topic?
Yes β pick "Correction / new topic" in the dropdown. Corrections to anything on these pages are very welcome.