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
/healththat 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
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.
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):
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.
// 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.
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:
- Heap snapshots —
node --inspectand Chrome DevTools. Take heap snapshots before and after; compare. - Monitor —
process.memoryUsage()in logs. - Common causes — Global closures holding references, unclosed DB connections, event listeners not removed.
- Tools —
clinic.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
| Topic | Key takeaway |
|---|---|
| High availability | Redundancy, health checks, graceful degradation |
| Idempotency | Same request twice = same result once |
| Connection pooling | Reuse connections; limit max |
| N+1 | Eager load or batch queries |
| Rate limiting | Redis + counter per IP/window |
| CAP | Choose CP or AP based on use case |
| Memory leaks | Heap snapshots, monitor, close connections |
| Microservices | Saga, outbox, eventual consistency |
For system design questions, see System Design Interview Questions.