Loading...
Loading...
Backend interview guide
Scaling, caching, databases, queues, load balancing and designing common systems. Every answer is short enough to say aloud, and every question links to the lesson that teaches it.
61 questionsLast updated
Product descriptions change rarely and an old one is harmless, so 10 minutes with deletion on update is fine. Order status must be close to real time, so I would delete on every status change and keep any TTL to a few seconds, or not cache it at all.
Lesson: Cache-aside, write-through and expiryBecause the cache and the database are two separate stores with no shared transaction. Updates can be forgotten, fail half way or arrive in a different order from different servers, and each of those leaves a copy that disagrees with the database.
Lesson: Cache invalidation and stale dataAlmost. A slow reader can load the old value before the update and put it back after the delete. That window is tiny, and I limit it with a TTL on every entry, plus a second delete a short time after the update for data where it matters.
Lesson: Cache invalidation and stale dataI would check which cache keys hold that product: the product key, category lists, search results and home page blocks. Most likely the update deletes only the product key. I would add one method that lists all affected keys and call it from every update path, or shorten the TTL of list pages.
Lesson: Cache invalidation and stale dataThe entry stays stale until its TTL expires, which is why every entry needs one. For important data I would retry the delete, or publish the change as an event so a background worker deletes the key again.
Lesson: Cache invalidation and stale dataI would extend LinkedHashMap with accessOrder = true in the constructor and override removeEldestEntry to return size() > capacity. Access order keeps the least recently used entry first, and the hook removes it after each put.
LRU evicts what was used longest ago; LFU evicts what was used the fewest times. LFU protects steady favourites from a burst of one-time reads, but an item that was popular last week can stay forever unless counts decay. LRU adapts faster when the hot set changes.
Lesson: Eviction policies: LRU and TTLNo. With access order, even get changes the internal links, so two threads can corrupt it. I would wrap it with Collections.synchronizedMap for low traffic, or use a concurrent cache library such as Caffeine for real servers.
@Cacheable do?It makes Spring check a named cache before running the method, using a key built from the arguments. On a hit it returns the cached value and skips the method; on a miss it runs the method and stores the result. It is cache-aside done by the framework.
Lesson: Spring @Cacheable and Redis as a shared cache@Cacheable not work even though it is on the method?Most often because of self-invocation: the method is called from another method of the same class, so the call does not go through Spring's proxy. Other causes are a missing @EnableCaching, or calling the method on an object you created with new instead of the Spring bean.
Every cache read is a network call, so it is slower than a local map, and Redis becomes one more system that can fail. The app should treat Redis errors as cache misses and fall back to the database instead of failing the request.
Lesson: Spring @Cacheable and Redis as a shared cacheA DTO such as ProductResponse. An entity can have lazy relations that fail to load outside a transaction, and it can be large or change shape. A small, Serializable (or JSON-friendly) DTO is safe to store and read back on any server.
It is when a popular key expires and many requests miss at the same time, so all of them hit the database with the same query. I prevent it with one loader per key, such as computeIfAbsent or @Cacheable(sync = true), and by refreshing very hot keys early.
Probably not. With a 1 ms hit and a 51 ms miss, the misses contribute 5.1 ms of the 6 ms average. Raising the hit ratio to 99% gives a bigger win, and it also cuts database load ten times.
Lesson: Caching practiceEvery input that changes the result: for a search, the query, category, page, page size and sort, and the user id if the result is personal. A key that leaves out an input makes different requests share one entry and return wrong data.
Lesson: Caching practiceYes. A cache with a low hit ratio adds a cache check to almost every request without saving database work. Bad keys, huge values or a stampede after expiry can also add load. That is why I measure the hit ratio before and after adding a cache.
Lesson: Caching practiceAn index can turn a scan of millions of rows into a few reads, which is a hundred or thousand times faster, for the cost of one statement. A bigger machine gives maybe two or four times more power, and replicas would all run the same full scan.
Lesson: Indexes and query tuning as the first stepFirst type, where ALL means a full scan; then key, the index chosen; then rows, how many rows MySQL expects to examine. A big rows number with ALL on a frequent query is the first thing to fix.
Not efficiently, because the index is sorted by status first and ordered_at only inside each status. The leftmost column must be in the filter. If that query is frequent, I would add an index that starts with ordered_at.
Opening a database connection needs network handshakes, login and session setup, which can cost more than the query. A pool opens a fixed number of connections once and lends them to requests, so requests are faster and the database is protected from too many connections.
Lesson: Connection pools and their limitsI start from how many connections are busy on average, requests per second times hold time, add headroom for peaks, and check that servers times pool size stays below the database's max_connections. HikariCP's guide suggests a small pool near cores × 2 plus disks for the database server; more than that usually just adds contention.
Long transactions and leaks. I look for slow outside calls such as payment gateways or emails inside @Transactional methods, and code paths that do not close connections on errors. HikariCP's leak detection threshold setting can log where a connection was borrowed and not returned.
A read replica is a copy of the database that applies the primary's changes and serves reads. I add one when reads are the main load on the primary, as in a shop where product pages are read far more than orders are written, and after indexes and caching are already in place.
Lesson: Replication and read replicasI read the user's own recently changed data from the primary, for example for a couple of seconds after they write, or always for pages like "My orders". Other options are waiting until a replica reaches the write's log position, or showing the data from the write's response.
Lesson: Replication and read replicasA failover tool or the managed service promotes the replica that has applied the most changes, points the other replicas at it and switches the write endpoint. Writes not yet copied can be lost with async replication, and in-flight requests fail, so clients need safe retries with idempotency keys.
Lesson: Replication and read replicasA common way is an AbstractRoutingDataSource with two targets, primary and replica, that picks the replica when the current transaction is @Transactional(readOnly = true). A database proxy such as ProxySQL can also split reads and writes outside the app.
I pick a column that most queries filter on, so they hit one shard, and that has many values spread evenly, so no shard is hot. For ShopLite orders that is customer_id. I avoid low-cardinality columns like status, ever-growing ranges like a new id, and keys where one value can be huge.
Shards and keys are hashed onto the same ring of values. A key belongs to the first shard point clockwise from it. Each shard has many virtual nodes so the load is even. Adding a shard only takes keys from just before its points, so about 1/N of keys move, all to the new shard. In Java I can build it with a TreeMap and ceilingEntry.
Only after indexes, caching and read replicas are not enough, usually because the primary cannot take the write rate or the data no longer fits one server. Even then I would first move big append-only tables, like old orders, to partitions or an archive.
Lesson: Sharding and partitioning (concept)I start from the data and queries. If the data is relational and changes need transactions, like orders and payments, I use a relational database, which is also the safe default. I add a NoSQL store when one data set clearly fits it: key-value for sessions, documents for varying product specs, wide-column for huge event streams.
Lesson: SQL vs NoSQL and the CAP theoremIf a database is copied on two servers and the network between them breaks, each side must either refuse requests it cannot confirm, which is consistency, or answer anyway and risk old or conflicting data, which is availability. For stock at checkout I would choose consistency; for a product view counter, availability.
Lesson: SQL vs NoSQL and the CAP theoremIt means that if no new writes arrive, all copies will become the same after some time, but a read right after a write may see an old value. It is fine for things like likes, view counts or recommendations, but not for balances or stock at checkout.
Lesson: SQL vs NoSQL and the CAP theoremIf each value is on N nodes, a write waits for W acknowledgements and a read asks R nodes, then R + W > N guarantees that the read set overlaps the latest write, so the newest version is seen. For N = 3, W = 2 and R = 2 is a common balance.
Lesson: SQL vs NoSQL and the CAP theoremIn a replicated database, if the network splits the servers, each side must either refuse requests it cannot confirm, which keeps consistency, or keep answering and risk stale or conflicting data, which keeps availability. Partitions cannot be avoided, so the choice is C or A during one. Payments lean to consistency, view counters to availability.
Lesson: Databases at scale practiceI would use customer_id, hashed, because the main queries are per customer and customers are many and spread evenly. I would test the distribution on real data and avoid status or date ranges, which create hot shards. For a few giant customers I would split their key into buckets.
When a data set clearly fits another model: Redis for sessions, carts and caches with TTL; a document store or JSON column for product specs that vary; a wide-column or analytics store for event streams. Orders, payments and stock stay in MySQL for transactions.
Lesson: Databases at scale practiceWith hash % N, adding one node changes the home of most keys, so almost all data moves. Consistent hashing places nodes and keys on a ring, so a new node only takes about 1/N of the keys. Virtual nodes keep the shares even.
Vertical scaling makes one machine bigger: more CPU, memory or faster disk. Horizontal scaling adds more machines and spreads requests with a load balancer. Vertical is simpler but has a ceiling and a single point of failure; horizontal grows further and survives failures, but needs stateless servers.
Lesson: Vertical vs horizontal scalingThe server must be stateless: no sessions, carts or uploaded files kept locally, scheduled jobs that run once, and all servers sharing the same database and cache. Then the load balancer can send any request to any copy.
Lesson: Vertical vs horizontal scalingIt watches a metric such as average CPU or requests per server and adds servers above a threshold, removing them when load falls, between a minimum and a maximum. It reacts after the load arrives and new servers take minutes to be ready, so planned spikes need pre-scaling, and the maximum must respect database limits.
Lesson: Vertical vs horizontal scalingIt gives clients one address and spreads their requests over several servers, skipping servers that are down. That lets us add or remove servers without clients noticing and survive a server failure.
Lesson: Load balancer algorithms: round robin, least connectionsI keep an AtomicInteger counter and pick servers.get(Math.floorMod(counter.getAndIncrement(), servers.size())). The atomic increment gives every thread its own number without a lock, and floorMod keeps the index valid after the counter overflows.
When servers have different capacity, for example during a migration to bigger machines. Weights matching their size make each server equally busy instead of overloading the small ones.
Lesson: Load balancer algorithms: round robin, least connectionsInstead of checking every server, the balancer picks two at random and sends the request to the less loaded one. It keeps loads nearly as even as least connections while needing very little information, which suits large fleets and many balancer copies.
Lesson: Load balancer algorithms: round robin, least connectionsActive health checks: it calls a health endpoint every few seconds and marks the server down after a few failures in a row, and up again after a few successes. Many balancers also use passive checks, ejecting a server that returns too many errors or timeouts on real traffic.
Lesson: Health checks and failoverRun two or more. A classic setup is active-passive with a shared virtual IP: the standby takes over the address when heartbeats stop. Cloud load balancers do this for you across several zones, and DNS can also point at several balancers.
Lesson: Health checks and failoverIt is when two nodes both believe they are the primary and both accept writes, for example an old database primary returning after a failover. Fencing prevents it: each promotion raises an epoch or token number, and storage or the other nodes reject writes carrying an older one.
Lesson: Health checks and failoverBy not keeping session state in the server. I store sessions in Redis, for example with Spring Session, or use short-lived signed JWTs so each request carries its own authentication. Carts go in Redis or the database. Then any server can take any request.
Lesson: Sticky sessions and CDNs for static contentProduct images, JavaScript, CSS, fonts and icons, all with content-hashed names and a one-year cache. Possibly short-TTL public API responses like category lists. Never personal or authenticated responses such as carts, orders or login tokens.
Lesson: Sticky sessions and CDNs for static contentThe browser loads the HTML and versioned assets and images from the nearest CDN edge, which goes to the origin only on a miss. The page then calls /api/v1/products/1, which goes to the load balancer, then to any stateless app server, which reads Redis and MySQL and returns JSON.
Keep the counter in an AtomicInteger and use getAndIncrement(), compute the index with Math.floorMod so it survives overflow, and keep the server list immutable, replacing it as a whole when servers change.
All static assets with content-hashed names, such as JS, CSS, images and fonts, with a one-year cache, and the HTML with no-cache. Never personal or login responses. Public API data only with a short max-age if slightly stale data is acceptable.
With graceful shutdown, each old server first fails readiness so the balancer stops sending it new requests, finishes the ones it has within a timeout, and then exits. A rolling deploy replaces servers a few at a time, so capacity stays up.
Lesson: Load balancing and scaling practiceRate limiting rejects a client's extra requests with 429. Throttling delays them instead, so the client is slowed down but not refused. Load shedding works at the server level: when the whole server is overloaded, it drops low-priority work for everyone, usually with 503, to keep important paths like checkout alive.
Lesson: Why rate limitNo. A common source of overload is your own client: a mobile app release that retries failed calls in a tight loop, or a partner's script with a bug. Limits protect you from mistakes as much as from attacks.
Lesson: Why rate limitRejecting is cheap: a counter lookup in memory, taking microseconds. Serving the request might need database queries and several milliseconds. So the server spends almost nothing on the abusive traffic and keeps its real capacity for real users; putting the limit at the gateway saves even that.
Lesson: Why rate limitWith a fixed window, a client can use the whole limit at the very end of one window and again at the start of the next. So with "100 per minute" it can send 200 requests within a couple of seconds, even though each window only saw 100.
Lesson: Fixed window, sliding window and token bucketIt keeps the previous and current window counts. If the previous minute had 80 requests, the current has 30, and we are 25% into the current minute, the estimate is 80 × 0.75 + 30 = 90. It assumes the previous minute's traffic was even, so it is approximate, but it needs only a few numbers per client.
Lesson: Fixed window, sliding window and token bucketA sliding window counter or a token bucket. Both use a couple of numbers per key, and with a limit that high a log of 1,000 timestamps per key would waste memory. A token bucket also lets well-behaved clients send short bursts, which is friendly for real apps.
Lesson: Fixed window, sliding window and token bucketIt has capacity, tokens, refillPerSecond and lastRefill. tryAcquire first refills: it adds elapsed × rate tokens, caps them at the capacity and sets lastRefill to now. Then, if at least one token is there, it takes one and returns true; otherwise false.
Both public methods are synchronized, so the read-check-change of tokens happens as one step. Each bucket has its own lock and the work inside is a few arithmetic steps, so different users never wait for each other and one user's threads wait for well under a microsecond.
Each lesson has quizzes, practice and more interview questions on the same topic.
Open the first lesson