ZTechUniverse

Senior Backend Developer Interview Questions with Answers

2025-02-20 · 5 min read

TL;DR — Senior backend interviews focus on architecture, trade-offs, scalability, and real-world problem-solving. Here are common questions with answers and code where applicable.


1. How do you design an API for high availability?

Answer: High availability means the system stays up even when components fail. Key strategies:

  • Redundancy — Multiple instances behind a load balancer. No single point of failure.
  • Health checks — Load balancer removes unhealthy instances. Use /health that checks DB, Redis, etc.
  • Graceful degradation — If cache is down, fall back to DB (slower but works).
  • Circuit breaker — Stop calling a failing service after N failures; retry later.

Code: Health check endpoint

TypeScript
app.get("/health", async (req, res) => { const checks = { database: await checkDb(), redis: await checkRedis(), }; const healthy = Object.values(checks).every(Boolean); res.status(healthy ? 200 : 503).json(checks); });

2. Explain idempotency. Why is it important for payments?

Answer: Idempotency means performing the same operation multiple times has the same effect as doing it once. For payments, a duplicate request (e.g. user double-clicks) must not charge twice.

Implementation: Client sends Idempotency-Key: uuid header. Server stores the result of the first request. Duplicate requests return the cached response.

TypeScript
const store = new Map<string, { status: number; body: unknown }>(); app.post("/payments", async (req, res) => { const key = req.headers["idempotency-key"] as string; if (!key) return res.status(400).json({ error: "Missing Idempotency-Key" }); const cached = store.get(key); if (cached) return res.status(cached.status).json(cached.body); const result = await paymentService.charge(req.body); store.set(key, { status: 201, body: result }); res.status(201).json(result); });

3. How do you handle database connection pooling?

Answer: Connection pooling reuses DB connections instead of opening a new one per request. Limits total connections and improves performance.

Node.js (pg):

TypeScript
const pool = new Pool({ host: "localhost", database: "mydb", max: 20, idleTimeoutMillis: 30000, }); // Each query uses a connection from the pool const result = await pool.query("SELECT * FROM users WHERE id = $1", [id]);

4. What is the N+1 query problem? How do you fix it?

Answer: N+1 query: 1 query to fetch a list, then N queries (one per item) to fetch related data. Example: fetch 100 orders, then 100 queries to fetch user for each order.

Fix: Use eager loading (JOIN or include) or batch queries.

TypeScript
// Bad: N+1 const orders = await Order.find(); for (const order of orders) { order.user = await User.findById(order.userId); // N queries } // Good: Single query with JOIN const orders = await Order.find().populate("user");

5. How would you implement rate limiting?

Answer: Limit requests per IP (or user) per time window. Use Redis to store counts. Reject when limit exceeded.

TypeScript
async function rateLimit(ip: string, limit: number, windowSec: number): Promise<boolean> { const key = `ratelimit:${ip}`; const count = await redis.incr(key); if (count === 1) await redis.expire(key, windowSec); return count <= limit; } app.use(async (req, res, next) => { const allowed = await rateLimit(req.ip, 100, 60); if (!allowed) return res.status(429).json({ error: "Too many requests" }); next(); });

6. Explain CAP theorem. What does your system choose?

Answer: CAP: Consistency, Availability, Partition tolerance. You can't have all three under a network partition.

  • CP — Consistency + Partition tolerance. e.g. MongoDB in strict mode, ZooKeeper. Prefer correctness over availability.
  • AP — Availability + Partition tolerance. e.g. Cassandra, DynamoDB. Prefer availability; eventual consistency.

Most web apps choose CP for critical data (payments) and AP for non-critical (feeds, caches).


7. How do you debug a memory leak in Node.js?

Answer:

  1. Heap snapshotsnode --inspect and Chrome DevTools. Take heap snapshots before and after; compare.
  2. Monitorprocess.memoryUsage() in logs.
  3. Common causes — Global closures holding references, unclosed DB connections, event listeners not removed.
  4. Toolsclinic.js, heapdump.

8. How do you ensure data consistency across microservices?

Answer:

  • Saga pattern — Choreography or orchestration. Each service has compensating actions if a step fails.
  • Outbox pattern — Write to DB + outbox table in same transaction. Separate process publishes to message queue.
  • Eventual consistency — Accept that data may be temporarily inconsistent. Use events and idempotent consumers.

Summary

TopicKey takeaway
High availabilityRedundancy, health checks, graceful degradation
IdempotencySame request twice = same result once
Connection poolingReuse connections; limit max
N+1Eager load or batch queries
Rate limitingRedis + counter per IP/window
CAPChoose CP or AP based on use case
Memory leaksHeap snapshots, monitor, close connections
MicroservicesSaga, outbox, eventual consistency

For system design questions, see System Design Interview Questions.