
20 Backend Questions I Wish I Knew Earlier (My Interview Prep Notes)
Table of Contents
I came across a post online listing 20 backend interview questions that the author swore by. As I read through them, I found myself nodding along — these are the same concepts that kept coming up in my own study and the conversations I have had with senior engineers.
I don’t claim to have mastered all of these. Some I learned the hard way through production incidents, some I am still deepening my understanding of. But I wanted to write them down with my own answers as a study exercise, and maybe it helps someone else who is preparing for backend interviews.
The questions fall into five areas: databases, concurrency, caching, APIs, and message brokers. Each answer is based on what I understand from practice and reading — not textbook definitions.
Databases and Query Performance
Most application bottlenecks I have encountered trace back to the database layer. Knowing how data moves in and out efficiently when tables grow large is non-negotiable.
1. What happens when you put an index on a random UUID column?
Random UUIDv4 inserts land all over the B-tree instead of appending in order.
- Causes constant page splits, fragmentation, and extra disk I/O as tables grow.
- Fix: use sequential keys or time-sorted UUIDs (ULID, UUIDv7) so inserts append to the tail of the index.
2. How do you paginate through 50 million rows without OFFSET?
OFFSET 100000 still reads and discards the first 100k rows before returning anything — the deeper the page, the slower the query.
- Use keyset (cursor) pagination:
WHERE id > :last_seen ORDER BY id LIMIT non an indexed column. - Query time stays flat at any depth. Tradeoff: you lose random jump-to-page-N.

3. When would you use a composite index instead of two separate indexes?
When queries filter on two columns together.
- Two single-column indexes: the planner usually picks only one per scan.
- A composite index on (A, B) filters both in a single seek.
- Column order matters — only the leftmost prefix is usable. (A, B) helps
WHERE AandWHERE A AND B, but notWHERE Balone.
4. What is the N+1 query problem and how do you fix it?
One query for the list, then one query per row for its related data — 101 queries for 100 rows.
- Fix: eager load or batch —
JOIN FETCH(JPA),.includes()(Rails), DataLoader (GraphQL). - Watch ORMs that lazy-load relations by default; the trap is invisible until it’s under load.

Concurrency and Transactions
Distributed systems do things at the same time. If you don’t manage that timing, things break silently.
5. How do you prevent double booking a ticket in a distributed system?
Check-then-book is a race: two requests both read “available,” both write.
- Application-level locks don’t span multiple servers.
- Fix at the database:
SELECT ... FOR UPDATE(pessimistic) or a version column (optimistic) that rejects stale writes. - Add a unique constraint on the seat as a hard backstop, regardless of locking strategy.

6. What is the difference between read committed, repeatable read, and serializable?
- Read committed (Postgres default): each statement sees the latest committed data, so reads can change within a transaction.
- Repeatable read: a stable snapshot for the whole transaction. The SQL standard still allows phantom reads here, but Postgres’s MVCC implementation also prevents them.
- Serializable: behaves as if transactions ran one at a time. It may abort with serialization errors you have to retry.
Use serializable when correctness matters more than throughput — financial ledgers, inventory systems.
7. How do you implement a distributed lock without creating a single point of failure?
A single Redis instance dies and takes your lock with it.
- Redlock: acquire on a majority quorum across independent Redis nodes.
- ZooKeeper / etcd: consensus protocols (ZAB, Raft) handle leader election and lock handoff more robustly.
- Pragmatic default is Redis; reach for etcd/ZooKeeper when you need strong guarantees. Always set a lock TTL so a dead holder doesn’t deadlock everyone.

8. How do you implement idempotency for a payment retry endpoint?
Mobile networks drop connections, clients retry, and a retried payment can charge twice.
- Client sends a unique idempotency key per logical request.
- Server: if the key was already processed, return the stored response; otherwise process and store the result against the key.
- Keep keys in a durable store (a database, not just Redis) with a TTL.
- Put a unique constraint on the key so concurrent retries can’t double-insert.

Caching Strategy
Caching makes things faster. It also introduces new failure modes that are easy to overlook until they hit production.
9. What causes a cache stampede and how do you prevent it?
A hot key expires and thousands of simultaneous misses hammer the database at once. Options:
- Per-key lock: one request rebuilds the value while the others wait or serve stale.
- Probabilistic early refresh: regenerate slightly before the TTL ends.
- Pre-warm the cache on deploy.

10. If you cache a user profile, how do you invalidate it when they update their email?
- Write-through: update cache and database together.
- Cache-aside: reads check the cache, misses go to the database and then populate it.
- For invalidation, deleting the key on update gives immediate consistency; a short TTL is simpler but leaves a stale window.
- Rule of thumb: explicit delete for user-facing data, TTL for low-stakes content.
11. Why might putting Redis in front of your database actually slow your system down?
Every cache check adds a 1-3ms network hop.
- With a low hit rate (say 20%), you pay that hop on every request and still hit the database most of the time — net slower than querying directly.
- Monitor the hit ratio; below ~80% the cache layer may cost more than it saves.
- Caches pay off on hot, read-heavy keys, not on uniformly random access.
12. What eviction policy makes sense for a session store versus a content feed?
- Content feed: LRU works well — users rarely revisit old items.
- Session store: LRU is risky — an active user can get evicted under memory pressure and logged out unexpectedly.
- Prefer TTL-based expiry or no-eviction with a size cap (and enough memory headroom) for sessions.
APIs and Network Architecture
Your API is a contract with clients you don’t control. Changing it safely and protecting it from abuse are baseline skills.
13. How do you safely change the payload of a live API without breaking existing mobile clients?
Mobile apps live on devices for years; you can’t force an upgrade.
- Additive changes (new optional fields) are backward-compatible.
- Breaking changes (removing/renaming fields) need a new version via URL path (
/v2/) or header (Accept: application/vnd.api.v2+json), with a deprecation window. - Never silently repurpose the meaning of an existing field.
14. What is the difference between a sliding window log and a fixed window counter for rate limiting?
- Fixed window: count requests per bucket (e.g., per minute). Cheap, but a client can burst 2x the limit right at the boundary.
- Sliding window log: track per-request timestamps and count only those in the rolling window. Smoother, but higher memory.
- Middle ground: a sliding window counter (weighted blend of two buckets) — cheap and accurate enough for most cases.
15. How do you design an endpoint that needs to upload a 5GB video file?
Reading 5GB into application memory kills the process. Three practical approaches:
- Presigned URLs: the client uploads directly to S3/GCS and your server never touches the bytes (best default).
- Streaming: pipe chunks straight to storage without buffering the whole file.
- Multipart chunking: the client splits the file into parts, uploads them in parallel, and can resume.
16. How do you handle long running tasks in a synchronous API request?
A 40-second PDF generation will time out the client connection. Use the async worker pattern:
- API returns
202 Acceptedimmediately with a job ID. - Client polls a status endpoint or receives a webhook on completion.
- The actual work runs in a background worker or queue.

Message Brokers and Asynchronous Processing
Real systems decouple work. You need to know what happens when those decoupled pieces fail — because they will.
17. Why would you choose RabbitMQ over Kafka, or vice versa?
They solve different problems.
- Kafka: an append-only distributed log — high throughput, ordered, replayable. Good for event streaming, audit trails, and fanning out to many consumers.
- RabbitMQ: a smart broker — flexible routing (exchanges, bindings) and per-message acknowledgment. Good for task queues and complex routing.
- Need replay or streams → Kafka. Need routing or per-message ack → RabbitMQ.

18. What happens if your Kafka consumer reads a message but fails to commit the offset?
The consumer re-reads the same message on restart — this is at-least-once delivery.
- So your processing must be idempotent.
- Common pattern: dedupe on a unique event ID before applying business logic.
- For stronger guarantees, commit the offset and side effects together (transactional outbox / exactly-once).
19. How do you handle poison messages that repeatedly crash your workers?
A malformed message crashes a worker, gets requeued, crashes the next one, and loops.
- Set a retry limit, then route the message to a Dead Letter Queue for inspection.
- Add exponential backoff between retries so you don’t flood the queue with immediate ones.
- Monitor DLQ depth — an unusual spike tells you something upstream is broken.
20. How do you ensure messages are processed in the exact order they were sent?
Guaranteeing global order across multiple consumers is impractical — consumer A might take longer on message 1 than consumer B takes on message 2.
- Use partition or routing keys: messages with the same key go to the same partition and are consumed in order by a single thread.
- You get per-key ordering while keeping parallelism across keys.
- Tradeoff: a hot key becomes a throughput bottleneck.
My Takeaway After Writing This
Going through these 20 questions was a useful exercise. What I noticed is that every single one maps to a specific way that production systems break — slow queries under load, race conditions during concurrent writes, cache failures under traffic spikes, broken clients after API changes, and lost or duplicated messages in async pipelines.
I don’t think memorizing answers is the point. Understanding why these problems happen and what the tradeoffs are — that is what makes these questions useful. And honestly, half of these I only understood properly after encountering something similar in a real system.
If you are preparing for backend interviews, or just want to pressure-test your own understanding, I found it helpful to try explaining each one out loud. If you can explain it conversationally without reaching for textbook phrasing, you probably understand it well enough.