InterviewHack.ai
Start free
Blog/Backend Developer Interview Questions: Real Answers and Strategies (50+)

Backend Developer Interview Questions: Real Answers and Strategies (50+)

August 7, 2026

backendapi
Backend Developer Interview Questions: Real Answers and Strategies (50+)

A comprehensive guide covering 50+ backend developer interview questions with detailed answers, real code examples in Python, JavaScript, Java, SQL, and Go, and strategic advice for each question type — from data structures and databases to system design, concurrency, security, and architecture.

Backend Developer Interview Questions: Real Answers and Strategies (50+)

Preparing for a backend developer interview means facing a wide range of questions — from data structures and algorithms, to system design, databases, APIs, security, and concurrency. This guide covers 50+ of the most commonly asked backend interview questions with detailed answers, real code examples, and strategic tips for each.

Whether you are interviewing at a startup or a FAANG-tier company, these answers reflect what senior engineers actually look for.


How Backend Interviews Are Structured

Most backend interviews follow a predictable arc:

| Round | What They Test |

|-------|---------------|

| Phone screen | Basic syntax, problem-solving approach |

| Technical 1 | Data structures, algorithms, runtime complexity |

| Technical 2 | System design, database modeling, API design |

| Technical 3 | Concurrency, security, architecture decisions |

| Final / culture | Communication, decision-making under constraints |

The biggest mistake candidates make is memorizing answers without understanding the tradeoffs. Interviewers are not looking for a perfect recitation — they are looking for how you think.


Section 1: Core Computer Science Fundamentals

1. What is the difference between a process and a thread?

A process is an independent program in execution with its own memory space, file descriptors, and system resources. A thread is a lighter unit of execution that shares memory space with other threads in the same process.

Key differences:

| | Process | Thread |

|--|---------|--------|

| Memory | Isolated address space | Shared heap, own stack |

| Communication | IPC (pipes, sockets, shared memory) | Direct memory access |

| Context switch cost | High | Low |

| Crash impact | Isolated | Can crash entire process |

| Creation cost | Expensive (fork/exec) | Cheap (pthread_create) |

Why it matters in backend: Web servers like Nginx use a process-per-worker model for isolation, while Node.js uses a single-threaded event loop. Java and Go use threads heavily for concurrent I/O.


2. Explain Big O notation with practical backend examples.

Big O notation describes the worst-case growth rate of an algorithm's time or space requirements relative to input size. It tells you how the algorithm scales.

O(1)    — Constant time       → Hash map lookup
O(log n) — Logarithmic        → Binary search, B-tree index lookup
O(n)    — Linear              → Scanning an unsorted list
O(n log n) — Linearithmic     → Merge sort, most efficient comparison sorts
O(n²)   — Quadratic           → Nested loops, naive duplicate detection
O(2^n)  — Exponential         → Recursive Fibonacci without memoization

Practical example — why database indexes matter:

Without an index, a SELECT WHERE query scans every row: O(n). With a B-tree index, the database traverses a balanced tree: O(log n). For a table with 10 million rows, the difference is 10,000,000 comparisons vs. about 23.

sql
-- Without index: full table scan O(n)
SELECT * FROM users WHERE email = 'user@example.com';

-- After: CREATE INDEX idx_users_email ON users(email);
-- Now: B-tree traversal O(log n)

Interview trap: candidates often say "O(n)" when describing a nested loop that's actually O(n²). Always count the number of times the innermost operation executes.


3. What are the main data structures and when do you use each?

Array        → When you need O(1) random access by index, fixed-size data
Linked List  → When you need O(1) insertion/deletion at head, no random access needed
Hash Map     → When you need O(1) key-value lookup (average case)
Binary Tree  → When you need sorted data with O(log n) search, insert, delete
Heap         → When you need O(1) access to the min or max element
Stack        → LIFO: function call stack, expression parsing, undo history
Queue        → FIFO: job queues, BFS traversal, request buffering
Graph        → Representing relationships, network topology, dependencies
Trie         → Prefix-based search, autocomplete, routing tables

Backend-specific usage:

  • Redis uses hash maps internally for its hash data type and skip lists for sorted sets
  • PostgreSQL B-tree index is a balanced binary tree variant
  • Message queues (RabbitMQ, Kafka) are producer-consumer queues at scale
  • DNS resolution and URL routing use trie structures

4. What is recursion and when would you avoid it in production code?

Recursion is a function that calls itself with a reduced subproblem until reaching a base case.

python
# Classic example: factorial
def factorial(n):
    if n <= 1:           # base case
        return 1
    return n * factorial(n - 1)  # recursive case

# factorial(5) → 5 * factorial(4) → 5 * 4 * factorial(3) → ...

When to avoid recursion in production:

  1. 1Deep call stacks cause stack overflow. Python's default recursion limit is 1,000. Processing a 100,000-node linked list recursively will crash.
  1. 2Use iteration instead for linear problems:
python
# Bad for production: O(n) stack depth
def sum_list_recursive(lst, idx=0):
    if idx == len(lst):
        return 0
    return lst[idx] + sum_list_recursive(lst, idx + 1)

# Good: constant stack space
def sum_list_iterative(lst):
    total = 0
    for x in lst:
        total += x
    return total
  1. 3Tail-call optimization (TCO) makes recursion safe in languages like Scheme and Elixir. Python and Java do not implement TCO.

When recursion is appropriate:

  • Tree traversal (depth naturally bounded by tree height)
  • Divide-and-conquer algorithms (merge sort, quicksort)
  • Graph DFS on bounded-depth graphs

Section 2: Databases

5. Explain ACID properties with a real example.

ACID is the set of properties that guarantee database transactions are processed reliably.

A — Atomicity: The transaction is all-or-nothing. Either all operations commit or none do.

sql
BEGIN;
  UPDATE accounts SET balance = balance - 500 WHERE id = 1;  -- debit
  UPDATE accounts SET balance = balance + 500 WHERE id = 2;  -- credit
COMMIT;
-- If the second UPDATE fails, the first is rolled back automatically

C — Consistency: A transaction brings the database from one valid state to another. Constraints (foreign keys, CHECK constraints, NOT NULL) are enforced.

I — Isolation: Concurrent transactions execute as if they were sequential. One transaction's intermediate state is invisible to other transactions (at the appropriate isolation level).

D — Durability: Once a transaction commits, it persists — even if the server crashes immediately after. Achieved via write-ahead logs (WAL).

Real interview follow-up: "Can you relax ACID for performance?" Yes — isolation levels (READ UNCOMMITTED, READ COMMITTED, REPEATABLE READ, SERIALIZABLE) trade safety for throughput. NoSQL databases often relax durability or consistency for availability (CAP theorem).


6. What is the N+1 query problem and how do you fix it?

The N+1 problem occurs when fetching a list of N records and then making 1 additional query per record — N+1 database queries total instead of 1 or 2.

python
# BAD: N+1 problem
posts = Post.query.all()          # 1 query: SELECT * FROM posts
for post in posts:
    author = post.author          # N queries: SELECT * FROM users WHERE id = ?
    print(f"{post.title} by {author.name}")

This produces 1 + N queries. For 1,000 posts, that is 1,001 database round trips.

Fix 1: JOIN / eager loading

python
# SQLAlchemy eager load with joinedload
posts = Post.query.options(joinedload(Post.author)).all()
# Single query: SELECT posts.*, users.* FROM posts JOIN users ON ...

Fix 2: Batch fetch with IN

sql
-- Fetch all post IDs, then fetch all authors in one IN query
SELECT * FROM users WHERE id IN (1, 5, 12, 47, 103, ...);

Fix 3: At the API layer (GraphQL)

GraphQL's DataLoader pattern batches and caches per-request:

javascript
const userLoader = new DataLoader(async (userIds) => {
  const users = await db.query(
    'SELECT * FROM users WHERE id = ANY($1)',
    [userIds]
  );
  return userIds.map(id => users.find(u => u.id === id));
});

// Even if called 1000 times in one request, fires ONE SQL query
const author = await userLoader.load(post.userId);

7. When do you use a relational database vs. a document database?

| Criterion | Relational (PostgreSQL, MySQL) | Document (MongoDB, Firestore) |

|-----------|-------------------------------|-------------------------------|

| Data shape | Fixed schema, normalized, related tables | Flexible, nested documents |

| Query type | Complex JOINs, aggregations, ad hoc SQL | Key lookups, simple filters |

| Transactions | Full ACID across tables | Limited (MongoDB added multi-doc transactions in v4.0) |

| Schema changes | Migrations required | Schema-less (but schema-on-read adds complexity) |

| Scaling | Vertical primarily; horizontal via replication | Horizontal sharding built-in |

| Best for | Financial data, e-commerce, user accounts | Content management, event logs, catalogs |

Interview answer framing: Start with your data's access patterns. If you need to JOIN across entities consistently (users → orders → products), a relational database is usually simpler to query correctly. If each entity is mostly self-contained and you fetch it whole, a document store reduces JOIN complexity.


8. Explain database indexing. When does an index hurt rather than help?

An index is a separate data structure (B-tree by default in PostgreSQL/MySQL) that the database maintains alongside a table to speed up reads.

sql
-- Without index: full sequential scan of all rows
EXPLAIN SELECT * FROM orders WHERE customer_id = 12345;
-- Seq Scan on orders  (cost=0.00..45000.00 rows=1 width=120)

-- Create index
CREATE INDEX idx_orders_customer_id ON orders(customer_id);

-- Now: index scan
EXPLAIN SELECT * FROM orders WHERE customer_id = 12345;
-- Index Scan using idx_orders_customer_id  (cost=0.43..8.45 rows=5 width=120)

When indexes hurt:

  1. 1Heavy write workloads. Every INSERT, UPDATE, or DELETE must update all relevant indexes. A table with 10 indexes on it takes ~10x longer to write to.
  1. 2Low-cardinality columns. An index on a boolean column (true/false) is usually useless — the optimizer may choose a full table scan anyway because half the rows match.
  1. 3Small tables. The optimizer will skip the index and do a sequential scan; sequential scans are faster for small tables because they avoid random I/O.
  1. 4Unselective predicates. WHERE status = 'active' when 95% of rows are active — the index provides almost no benefit.

Composite indexes — column order matters:

sql
-- Good for queries filtering on (customer_id, created_at)
CREATE INDEX idx_orders_customer_date ON orders(customer_id, created_at);

-- This query CAN use the index (leading column present)
SELECT * FROM orders WHERE customer_id = 123 AND created_at > '2024-01-01';

-- This query CANNOT efficiently use the index (leading column skipped)
SELECT * FROM orders WHERE created_at > '2024-01-01';

9. What is database normalization? When do you denormalize?

Normalization is the process of organizing a relational database to reduce data redundancy and improve data integrity.

Normal Forms (simplified):

  • 1NF: Every cell contains a single atomic value; no repeating groups
  • 2NF: 1NF + every non-key attribute depends on the entire primary key (no partial dependencies)
  • 3NF: 2NF + no transitive dependencies (non-key attribute depends on another non-key attribute)

Example — moving from unnormalized to 3NF:

sql
-- Unnormalized: customer data repeated per order
orders(order_id, customer_name, customer_email, customer_city, product, amount)

-- 3NF: split into related tables
customers(customer_id, name, email, city)
orders(order_id, customer_id, amount, created_at)
order_items(order_item_id, order_id, product_id, quantity, unit_price)
products(product_id, name, description, base_price)

When to denormalize:

Denormalization trades data redundancy for read performance. Use it when:

  1. 1Read-heavy workloads where JOINs are too slow at scale
  2. 2Reporting/analytics queries that aggregate across millions of rows
  3. 3Pre-computed aggregates that would require expensive GROUP BY every time
sql
-- Denormalized: store pre-computed order_total on the orders row
-- instead of summing order_items every time
ALTER TABLE orders ADD COLUMN total_amount DECIMAL(10, 2);

-- Keep it in sync via trigger or application logic

Data warehouses (Redshift, BigQuery, Snowflake) are deliberately denormalized — star schema / snowflake schema — because analytical queries favor wide tables over normalized joins.


10. What is a database transaction isolation level? Explain each level.

Isolation levels control how and when the changes made by one transaction become visible to other transactions.

| Level | Dirty Read | Non-Repeatable Read | Phantom Read | Use Case |

|-------|:----------:|:-------------------:|:------------:|---------|

| READ UNCOMMITTED | Possible | Possible | Possible | Almost never — very unsafe |

| READ COMMITTED | Prevented | Possible | Possible | PostgreSQL default; most web apps |

| REPEATABLE READ | Prevented | Prevented | Possible | Reporting queries; MySQL InnoDB default |

| SERIALIZABLE | Prevented | Prevented | Prevented | Financial transactions; max isolation |

Definitions:

  • Dirty read: Reading another transaction's uncommitted changes (which might be rolled back)
  • Non-repeatable read: Re-reading the same row yields different values because another transaction committed a change between reads
  • Phantom read: A range query returns different rows on re-execution because another transaction inserted/deleted rows in that range
sql
-- Example: READ COMMITTED (PostgreSQL default)
-- Transaction A
BEGIN;
SELECT balance FROM accounts WHERE id = 1; -- returns 1000

-- Transaction B (concurrent)
BEGIN;
UPDATE accounts SET balance = 500 WHERE id = 1;
COMMIT;

-- Transaction A re-reads
SELECT balance FROM accounts WHERE id = 1; -- returns 500 (non-repeatable read)
COMMIT;
sql
-- Fix: use REPEATABLE READ if consistency across reads is required
BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ;
SELECT balance FROM accounts WHERE id = 1; -- returns 1000
-- ...other work...
SELECT balance FROM accounts WHERE id = 1; -- still returns 1000
COMMIT;

11. What are SQL window functions and why are they powerful?

Window functions perform calculations across a set of rows related to the current row — without collapsing the rows into groups like GROUP BY does.

sql
-- Find each employee's salary and the average salary in their department
SELECT
  name,
  department,
  salary,
  AVG(salary) OVER (PARTITION BY department) AS dept_avg,
  salary - AVG(salary) OVER (PARTITION BY department) AS diff_from_avg,
  RANK() OVER (PARTITION BY department ORDER BY salary DESC) AS dept_rank
FROM employees;

Common window functions:

  • ROW_NUMBER() — unique sequential number per window
  • RANK() / DENSE_RANK() — ranking with/without gaps for ties
  • LAG() / LEAD() — access previous/next row's value
  • SUM() OVER (... ORDER BY ...) — running total
  • NTILE(n) — divide rows into n buckets (percentiles)

Why interviewers ask this: Window functions show SQL fluency beyond basic SELECT/JOIN. They are essential for reporting, analytics, and avoiding self-joins.


Section 3: API Design

12. What are the key principles of RESTful API design?

REST (Representational State Transfer) is an architectural style with six guiding constraints. For API design, these translate to:

1. Use nouns for resources, not verbs:

Bad:  POST /getUser       GET /createOrder
Good: GET  /users/{id}    POST /orders

2. HTTP methods carry semantic meaning:

GET    /orders          → list orders
POST   /orders          → create a new order
GET    /orders/123      → get a specific order
PUT    /orders/123      → replace order (full update)
PATCH  /orders/123      → partial update
DELETE /orders/123      → delete order

3. Use proper HTTP status codes:

200 OK           — successful GET, PUT, PATCH
201 Created      — successful POST
204 No Content   — successful DELETE
400 Bad Request  — client sent invalid data
401 Unauthorized — not authenticated
403 Forbidden    — authenticated but not authorized
404 Not Found    — resource does not exist
409 Conflict     — e.g., duplicate unique field
422 Unprocessable Entity — validation failed
500 Internal Server Error — something broke on the server

4. Versioning strategy:

/v1/users         — URL versioning (most common, explicit)
/users (header: Accept: application/vnd.myapi.v2+json) — header versioning

5. Pagination:

json
GET /orders?page=2&per_page=25

{
  "data": [...],
  "pagination": {
    "page": 2,
    "per_page": 25,
    "total": 1847,
    "next": "/orders?page=3&per_page=25",
    "prev": "/orders?page=1&per_page=25"
  }
}

13. What is the difference between REST, GraphQL, and gRPC? When do you use each?

| | REST | GraphQL | gRPC |

|--|------|---------|------|

| Protocol | HTTP/1.1 | HTTP/1.1 (usually) | HTTP/2 |

| Data format | JSON (typically) | JSON | Protocol Buffers (binary) |

| Typing | Informal (OpenAPI) | Strongly typed schema | Strongly typed (.proto) |

| Over-fetching | Common | Eliminated | Not applicable |

| Under-fetching | Requires multiple requests | Eliminated (single query) | Not applicable |

| Streaming | Limited (SSE/WebSocket) | Subscriptions | Native bidirectional |

| Browser support | Native | Native | Requires proxy (grpc-web) |

| Best for | Public APIs, simple CRUD | Complex data graphs, mobile clients | Internal microservices, high-throughput |

GraphQL example — fetching only what you need:

graphql
query {
  user(id: "123") {
    name
    orders(last: 3) {
      total
      status
    }
  }
}

gRPC example — .proto definition:

protobuf
service OrderService {
  rpc GetOrder (GetOrderRequest) returns (Order);
  rpc StreamOrders (StreamRequest) returns (stream Order);
}

Interview answer: Use REST for public APIs and simple services. Use GraphQL when clients have diverse data requirements (mobile vs. web vs. data teams). Use gRPC for internal microservice communication where performance and type safety matter.


14. How do you handle authentication and authorization in APIs?

Authentication (who are you?) and Authorization (what can you do?) are distinct — a common mistake is conflating them.

Authentication approaches:

Session cookies    → Stateful; good for web apps; CSRF risk
JWT (Bearer)       → Stateless; good for APIs; token revocation is hard
API Keys           → Good for server-to-server; less secure if exposed
OAuth 2.0          → Delegated access; use for third-party auth
mTLS               → Mutual TLS; excellent for internal microservices

JWT verification example:

javascript
const jwt = require('jsonwebtoken');

function authMiddleware(req, res, next) {
  const token = req.headers.authorization?.replace('Bearer ', '');
  if (!token) return res.status(401).json({ error: 'Missing token' });

  try {
    const payload = jwt.verify(token, process.env.JWT_SECRET);
    req.user = payload;
    next();
  } catch (err) {
    return res.status(401).json({ error: 'Invalid or expired token' });
  }
}

JWT security pitfalls to mention in interviews:

  • Store JWTs in httpOnly cookies (not localStorage) to prevent XSS theft
  • Short expiry + refresh tokens > long-lived tokens
  • Always validate exp, iss, and aud claims
  • Consider token blocklist in Redis for critical revocation (logout, password change)

15. How do you design an idempotent API?

An operation is idempotent if calling it multiple times produces the same result as calling it once. This is critical for payment systems, order creation, and any operation where network retries can cause duplicates.

Making POST idempotent with an idempotency key:

javascript
async function processPayment(req, res) {
  const key = req.headers['idempotency-key'];
  if (!key) return res.status(400).json({ error: 'Idempotency-Key required' });

  const cached = await redis.get(`idem:${key}`);
  if (cached) {
    return res.status(200).json(JSON.parse(cached));
  }

  const result = await chargeCard(req.body);
  await redis.setex(`idem:${key}`, 86400, JSON.stringify(result));
  return res.status(201).json(result);
}

Stripe uses exactly this pattern — their idempotency key docs are a good reference.


16. What is rate limiting and how do you implement it?

Rate limiting restricts how many requests a client can make in a time window — protecting your service from abuse, DDoS, and runaway clients.

Common algorithms:

| Algorithm | How It Works | Best For |

|-----------|-------------|---------|

| Fixed window | Count requests per minute/hour | Simple, has burst edge-case |

| Sliding window | Rolling 60-second window | More accurate, slightly more expensive |

| Token bucket | Tokens refill at fixed rate; request consumes token | Handles bursts gracefully |

| Leaky bucket | Requests queue and process at fixed rate | Smoothing bursts for downstream |

HTTP response headers to return:

X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 743
X-RateLimit-Reset: 1700000060
Retry-After: 30  (on 429 responses)

Section 4: System Design

17. How would you design a URL shortener (like bit.ly)?

Requirements:

  • Shorten a URL → unique 6-8 character code
  • Redirect short URL → original URL
  • Scale: 100M new URLs/day, 10B redirects/day

Encoding strategy:

python
def id_to_base62(n: int) -> str:
    chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'
    result = []
    while n:
        result.append(chars[n % 62])
        n //= 62
    return ''.join(reversed(result)) or '0'

Data model:

sql
CREATE TABLE short_urls (
    id          BIGSERIAL PRIMARY KEY,
    code        VARCHAR(10) UNIQUE NOT NULL,
    original_url TEXT NOT NULL,
    created_by  UUID,
    created_at  TIMESTAMPTZ DEFAULT NOW(),
    expires_at  TIMESTAMPTZ,
    click_count BIGINT DEFAULT 0
);
CREATE INDEX idx_short_urls_code ON short_urls(code);

Scaling the redirect path (10B/day = ~115K req/s):

  1. 1Redis cache: GET code → url with 24-hour TTL. Cache hit rate ~99% for popular URLs.
  2. 2Read replicas for the database fallback.
  3. 3Use 301 Permanent Redirect (cached by browser) vs 302 Temporary Redirect (not cached — better for analytics).
  4. 4CDN edge nodes can serve the redirect without hitting origin.

18. Explain caching strategies. What is cache invalidation and why is it hard?

Caching strategies:

Cache-aside (lazy loading)  → App reads cache first; on miss, reads DB, writes to cache
Write-through               → Every write goes to cache AND DB simultaneously
Write-behind (write-back)   → Write to cache immediately; DB write is async/batched
Read-through                → Cache sits in front of DB; cache handles fetching on miss

Cache-aside example:

python
async def get_user(user_id: str) -> dict:
    cache_key = f"user:{user_id}"
    cached = await redis.get(cache_key)
    if cached:
        return json.loads(cached)

    user = await db.fetch_one("SELECT * FROM users WHERE id = $1", user_id)
    if not user:
        return None

    await redis.setex(cache_key, 3600, json.dumps(dict(user)))
    return dict(user)

Why cache invalidation is hard:

Phil Karlton famously said: *"There are only two hard things in Computer Science: cache invalidation and naming things."*

The problem is consistency: when the source of truth changes, you must ensure the cache reflects that change — but timing, distributed caches with multiple versions, cascading dependencies, and TTL tradeoffs all make this non-trivial.

python
# Most common strategy: delete on write
async def update_user(user_id: str, data: dict):
    await db.execute("UPDATE users SET ... WHERE id = $1", user_id)
    await redis.delete(f"user:{user_id}")  # next read will re-populate

19. What is a message queue and why use one?

A message queue is a form of asynchronous communication between services. The producer sends a message and continues immediately; the consumer processes it independently.

Why use message queues:

| Problem | Queue Solution |

|---------|---------------|

| Slow external API (email, SMS) | Offload to background worker |

| Traffic spikes | Queue absorbs burst; consumer processes at its pace |

| Multiple consumers need the same event | Fan-out / pub-sub |

| Service is temporarily unavailable | Messages persist until consumer recovers |

| Long-running jobs | Decouple request from processing |

RabbitMQ vs. Kafka:

  • RabbitMQ: smart broker, dumb consumers; messages deleted after consumption; best for task queues
  • Kafka: dumb broker, smart consumers; messages retained as log; best for event streaming, replay, audit trails

20. Explain the CAP theorem.

CAP theorem states that a distributed system can guarantee at most two of these three properties simultaneously:

  • C — Consistency: Every read receives the most recent write or an error
  • A — Availability: Every request receives a response (not necessarily the most recent data)
  • P — Partition tolerance: The system continues operating despite network partitions

Since network partitions are unavoidable, you must choose between CP or AP when a partition occurs:

| Choice | Behavior during partition | Examples |

|--------|--------------------------|---------|

| CP | Returns error or timeout if data might be stale | HBase, Zookeeper, etcd |

| AP | Returns potentially stale data; eventual consistency | Cassandra, DynamoDB, CouchDB |


21. What is eventual consistency? Give a practical example.

Eventual consistency means that if no new updates are made to a value, all reads will eventually return the last written value — but at any given moment, different nodes may return different values.

Example: social media like count — when you like a post, users in other regions may see a count 1-2 seconds behind. That is acceptable. Eventually all nodes agree.

Eventual consistency OK:        Strong consistency required:
- Like/view counts              - Bank account balance after transfer
- Shopping cart recommendations - Inventory count during checkout
- Search indexes                - Seat reservation (avoid double-booking)
- User profile updates          - Auth tokens after logout

22. How would you design a distributed rate limiter?

A single-node rate limiter fails with multiple servers — each server has its own counter, so a client can bypass the limit by spreading requests.

Solution: Redis sliding window:

python
def is_rate_limited(client_id: str, limit: int, window_seconds: int) -> bool:
    now = time.time()
    window_start = now - window_seconds
    key = f"rate:{client_id}"

    pipe = r.pipeline()
    pipe.zremrangebyscore(key, '-inf', window_start)
    pipe.zcard(key)
    pipe.zadd(key, {str(now): now})
    pipe.expire(key, window_seconds)
    results = pipe.execute()

    request_count = results[1]
    return request_count >= limit

Section 5: Concurrency

23. What is a race condition? How do you prevent it?

A race condition occurs when two or more concurrent operations access shared state, and the outcome depends on the order of execution.

Prevention strategies:

sql
-- Pessimistic lock: SELECT FOR UPDATE
BEGIN;
SELECT balance FROM accounts WHERE id = 1 FOR UPDATE;
UPDATE accounts SET balance = balance - 500 WHERE id = 1;
COMMIT;

-- Optimistic: version column
UPDATE accounts
SET balance = balance - 500, version = version + 1
WHERE id = 1 AND version = 7;
-- If 0 rows updated: conflict → retry

24. What is a deadlock? How do you detect and prevent it?

A deadlock occurs when two or more threads/transactions are each waiting for a resource held by the other.

Prevention strategies:

  1. 1Consistent lock ordering: Always acquire locks in the same order (e.g., lower ID first)
  2. 2Lock timeouts: SET lock_timeout = '5s' in PostgreSQL
  3. 3Shorter transactions: Less time holding locks = less chance of deadlock
  4. 4Retry logic: PostgreSQL auto-detects deadlocks and raises error 40P01 — always handle with exponential backoff retry

25. What is async/await and how does it differ from multithreading?

Multithreading achieves concurrency with multiple threads. Async/await achieves concurrency through cooperative scheduling on a single thread — when I/O is awaited, control returns to the event loop.

python
# Async: all 3 HTTP requests run concurrently on one thread
async def fetch_all_async(urls):
    async with aiohttp.ClientSession() as session:
        tasks = [session.get(url) for url in urls]
        responses = await asyncio.gather(*tasks)
        return [await r.json() for r in responses]

Rule of thumb:

  • I/O-bound work → async/await; one thread handles thousands of concurrent I/O operations
  • CPU-bound work → threads/processes; async cannot parallelize CPU work on one thread

Section 6: Security

26. What is SQL injection and how do you prevent it?

Always use parameterized queries — never interpolate user input into SQL strings.

python
# NEVER DO THIS
query = f"SELECT * FROM users WHERE username = '{username}'"

# Always do this
db.execute("SELECT * FROM users WHERE username = %s", (username,))
javascript
// JavaScript with pg
const result = await pool.query(
    'SELECT * FROM users WHERE username = $1', [username]
);

Additional layers: ORM parameterization (SQLAlchemy, Hibernate, Prisma), principle of least privilege (no DROP/ALTER for app DB user), WAF as supplemental defense.


27. What is XSS and how do you prevent it?

XSS attacks inject malicious scripts into web pages that execute in other users' browsers.

Prevention:

  1. 1Output encoding — escape user-controlled content before rendering in HTML (template auto-escaping)
  2. 2Content Security Policy header: Content-Security-Policy: default-src 'self'; script-src 'self'
  3. 3httpOnly cookies: Set-Cookie: sessionid=abc; HttpOnly; Secure; SameSite=Strict

28. What is CSRF and how do you prevent it?

CSRF tricks a logged-in user's browser into making an unintended request to your backend.

Prevention:

  1. 1CSRF tokens — random, per-form token the attacker cannot know; validated server-side
  2. 2SameSite=Strict cookie attribute — prevents cookies from being sent on cross-origin requests
  3. 3Check Origin / Referer headers — reject requests where origin does not match your domain

29. How do you store passwords securely?

Never store plaintext. Never use MD5 or SHA-1 for passwords.

Use bcrypt or Argon2id — purposefully slow algorithms designed to be expensive to brute-force.

python
from passlib.context import CryptContext
pwd_context = CryptContext(schemes=["argon2"], deprecated="auto")

def hash_password(password: str) -> str:
    return pwd_context.hash(password)

def verify_password(plain: str, hashed: str) -> bool:
    return pwd_context.verify(plain, hashed)

Why not SHA-256? SHA-256 computes billions of hashes/second on a GPU. Argon2id is memory-hard — brute-force is impractical even with dedicated hardware. Salting is automatic in bcrypt/Argon2, preventing rainbow table attacks.


Section 7: Language-Specific Questions

30. Explain garbage collection. How does it affect backend performance?

| Strategy | How It Works | Language | Pause? |

|----------|-------------|---------|--------|

| Reference counting | Track reference count per object | Python, Swift | No stop-the-world; cycle detection needed |

| Mark-and-sweep | Mark reachable; sweep the rest | JavaScript (V8), Java, Go | Stop-the-world or concurrent |

| Generational | Young/old generations; most objects die young | Java (JVM), .NET | Short pauses for young gen |

GC affects backend via: stop-the-world pauses, memory pressure causing frequent GC runs, and p99 latency spikes dominated by GC cycles. Mitigation: tune JVM GC (ZGC for sub-millisecond pauses), reduce object allocation in hot paths, set initial = max heap to avoid resizing.


31. What are goroutines and how do they differ from threads?

go
func fetchData(url string, ch chan<- string) {
    resp, _ := http.Get(url)
    defer resp.Body.Close()
    body, _ := io.ReadAll(resp.Body)
    ch <- string(body)
}

func main() {
    urls := []string{"https://api1.com", "https://api2.com", "https://api3.com"}
    ch := make(chan string, len(urls))
    for _, url := range urls {
        go fetchData(url, ch)  // all 3 start concurrently
    }
    for range urls {
        fmt.Println(len(<-ch))
    }
}

| | OS Thread | Goroutine |

|--|----------|-----------|

| Stack size | 1-8 MB fixed | 2 KB initially, grows dynamically |

| Creation cost | ~1ms | ~1µs |

| Max per machine | ~10K | ~1M+ |

| Scheduling | OS kernel | Go runtime (M:N scheduler) |


32. Explain Python's GIL.

The GIL is a mutex in CPython that allows only one thread to execute Python bytecode at a time. It does NOT affect I/O-bound code (GIL is released during I/O). For CPU-bound parallelism, use multiprocessing, C extensions (NumPy), or Python 3.13's experimental free-threaded mode.


Section 8: System Architecture

33. What is the difference between microservices and monolithic architecture?

| | Monolith | Microservices |

|--|---------|---------------|

| Deployment | Single deployable unit | Each service deployed independently |

| Communication | In-process function calls | Network calls (HTTP, gRPC, queue) |

| Data | Single shared database | Each service owns its data |

| Best for | Early-stage, small teams | Large teams, independent scaling needs |

The monolith-first advice (Martin Fowler): start with a monolith. Break out services only when the monolith creates genuine pain — deployment coupling, team autonomy, or scaling bottlenecks.


34. What is the circuit breaker pattern?

Monitors calls to an external dependency. When failures exceed a threshold, trips "open" — subsequent calls immediately fail fast without hitting the dependency. After a timeout, enters "half-open" to test recovery.

CLOSED → failures < threshold → normal
OPEN   → fail fast immediately
HALF-OPEN → allow one test request → CLOSED (success) or OPEN (fail)

Libraries: Netflix Hystrix (Java), resilience4j (Java), Polly (.NET), pybreaker (Python).


35. How do you design a distributed lock?

python
class RedisLock:
    def acquire(self, timeout: int = 10) -> bool:
        deadline = time.time() + timeout
        while time.time() < deadline:
            acquired = self.redis.set(self.key, self.token, nx=True, ex=self.ttl)
            if acquired:
                return True
            time.sleep(0.1)
        return False

    def release(self):
        # Lua script: only release if we own the lock
        script = """
        if redis.call("get", KEYS[1]) == ARGV[1] then
            return redis.call("del", KEYS[1])
        else
            return 0
        end
        """
        self.redis.eval(script, 1, self.key, self.token)

Key points: random token per acquisition (prevent releasing another holder's lock), TTL prevents permanent deadlock, Redlock acquires across N Redis nodes for production use.


Section 9: Performance & Observability

36. Horizontal vs. vertical scaling?

Vertical: more resources on same machine — simple, but has a hardware ceiling and is a single point of failure.

Horizontal: more machines — requires stateless design (no server-side sessions, shared-nothing), load balancing, and externalized state (database, Redis, object storage).


37. How do you diagnose a slow API endpoint?

1. Measure: p50/p95/p99 latency — always slow or only under load?
2. Application traces: where is time spent? (DB, external calls, CPU?)
3. Database: EXPLAIN ANALYZE — full table scans? N+1 queries? Long-running transactions?
4. Infrastructure: CPU/memory/disk I/O on DB server; network latency
5. Code: blocking operations in async code? GC pressure?
sql
-- Find slowest queries
SELECT query, calls, round(total_exec_time / calls) AS avg_ms
FROM pg_stat_statements
WHERE calls > 100
ORDER BY avg_ms DESC LIMIT 20;

38. What is observability? Explain the three pillars.

Metrics — aggregated numerical measurements (request rate, error rate, p99 latency). Tools: Prometheus, Datadog.

Logs — time-stamped discrete events with structured JSON. Tools: Elasticsearch + Kibana, Loki.

Traces — end-to-end request journeys across services showing where latency is introduced. Tools: Jaeger, Zipkin, OpenTelemetry, Datadog APM.


39. What is a connection pool and why does it matter?

Creating a database connection costs 20-100ms (TCP handshake, TLS, auth). Connection pools maintain reusable open connections.

python
engine = create_engine(
    "postgresql://user:pass@db/myapp",
    pool_size=20,
    max_overflow=10,
    pool_timeout=30,
    pool_recycle=1800
)

With 10 app servers × 20 connections = 200 connections, which can exceed PostgreSQL's default limit of 100. PgBouncer solves this by multiplexing thousands of app connections onto a small pool of actual PostgreSQL connections.


Section 10: More Advanced Questions

40. Optimistic vs. pessimistic locking?

Pessimistic: SELECT ... FOR UPDATE — locks row at read time; other transactions wait. Best for high-contention scenarios.

Optimistic: version column checked at write time — fails if version changed; retry if 0 rows updated. Best for distributed systems and low-contention scenarios.


41. How does a CDN work?

Geographically distributed servers cache content near users. Cache behavior controlled by Cache-Control headers. Use CDN for static assets, video, large downloads, and public API responses. Do not use for user-specific, sensitive, or real-time data without cache purging strategy.


42. What is database sharding?

Horizontal partitioning — splitting data across multiple database instances. Common strategies: hash-based (shard_id = hash(user_id) % N), range-based, geographic.

Problems to discuss: cross-shard queries are expensive, rebalancing when adding shards (consistent hashing minimizes data moved), hot spots with time-based range sharding, and transactions across shards require 2-phase commit or eventual consistency.


43. What are the SOLID principles?

  • S: Single Responsibility — one reason to change per class
  • O: Open/Closed — open for extension, closed for modification (add classes, don't modify existing)
  • L: Liskov Substitution — subtypes must be substitutable for base types
  • I: Interface Segregation — many specific interfaces > one general-purpose interface
  • D: Dependency Inversion — depend on abstractions, not concrete implementations (inject dependencies)

44. How do you implement retry logic with exponential backoff?

python
def retry_with_backoff(func, max_retries=5, base_delay=1.0, max_delay=60.0, exceptions=(Exception,)):
    last_exception = None
    for attempt in range(max_retries):
        try:
            return func()
        except exceptions as e:
            last_exception = e
            if attempt == max_retries - 1:
                break
            delay = min(base_delay * (2 ** attempt), max_delay)
            jitter = random.uniform(0, delay * 0.1)
            time.sleep(delay + jitter)
    raise last_exception

Backoff sequence (base=1s): wait ~1s, ~2s, ~4s, ~8s, then raise. Jitter prevents synchronized retries across multiple clients (thundering herd).


45. What is a service mesh and when do you need one?

A dedicated infrastructure layer (Istio, Linkerd) handling mTLS, load balancing, circuit breaking, retries, and observability via sidecar proxies — without changing application code.

Use when: 10+ microservices, strict mTLS requirements, uniform observability needed without code changes. Skip when: fewer than ~5 services or team unfamiliar with Kubernetes — the operational complexity outweighs the benefit.


46. How do you handle conflicts in eventual consistency?

Conflict resolution strategies:

  1. 1Last write wins (LWW): use timestamps; simple but risks data loss and clock skew issues
  2. 2Application merge: e.g., shopping cart conflict → union of items, take max quantity
  3. 3CRDTs: mathematically convergent data structures (G-Counter, G-Set, LWW-Register)
  4. 4Human resolution: expose conflict to user (Dropbox, collaborative editors)

47. What is the saga pattern for distributed transactions?

Sagas manage multi-step transactions across services without 2-phase commit. Each step has a compensating transaction that undoes its effect on failure.

python
class OrderSaga:
    def execute(self, order_id):
        try:
            self.payment_service.charge(order_id)
        except PaymentError:
            self.order_service.cancel(order_id); return

        try:
            self.inventory_service.reserve(order_id)
        except InventoryError:
            self.payment_service.refund(order_id)  # compensate
            self.order_service.cancel(order_id); return

        self.order_service.confirm(order_id)

48. How does WebSocket work and when do you use it?

WebSocket provides a persistent, bidirectional TCP channel — both sides can send messages at any time without a new HTTP handshake per message. Use when the server needs to push to clients unprompted or when low-latency high-frequency updates are needed (live chat, collaborative editing, gaming, financial tickers). Use HTTP for request-response patterns where caching matters.


49. What are the most important things to do before deploying to production?

Code:        Code reviewed; tests passing; no secrets in code; no known CVEs
Performance: Load tested; no N+1 queries; indexes verified; memory profiled
Observability: Structured logs with trace IDs; dashboards updated; alerts configured
Operations:  Rollback plan ready; non-breaking DB migrations; feature flags available
Security:    Input validation; authorization checked; rate limiting on public endpoints

50. How do you approach designing a new system from scratch?

  1. 1Clarify requirements — core use case, read/write ratios, scale, availability, consistency, geography
  2. 2Define API surface — 3-5 core endpoints
  3. 3Sketch high-level components — clients → load balancer → API servers → [DB, cache, queue, CDN]
  4. 4Data model — key tables, hottest queries, index verification
  5. 5Scale the bottlenecks — sharding, read replicas, caching, async queues
  6. 6Discuss tradeoffs explicitly — every decision has a cost; show you understand both sides

Bonus: Interview Strategy

How to structure your answer

  1. 1Restate the problem
  2. 2State your approach before coding
  3. 3Walk through the solution while explaining
  4. 4Discuss time and space complexity
  5. 5Mention tradeoffs or alternatives

What interviewers actually evaluate

  • Communication: can you explain complex things clearly?
  • Tradeoff awareness: do you know *why* to make a choice, not just *how*?
  • Production mindset: edge cases, failure modes, observability?
  • Intellectual honesty: saying "I don't know" beats bluffing

Questions to ask your interviewer

- What does the data model look like for this service today?
- How does the team handle database migrations in production?
- What's the on-call rotation like? What are the most common pages?
- How do you approach technical debt?
- What does a typical deploy look like from commit to production?

Quick Reference: Complexity Cheat Sheet

| Operation | Data Structure | Average | Worst |

|-----------|---------------|---------|-------|

| Access | Array | O(1) | O(1) |

| Search | Array | O(n) | O(n) |

| Insert (end) | Array | O(1) | O(n) |

| Search | Hash Map | O(1) | O(n) |

| Insert | Hash Map | O(1) | O(n) |

| Search | Binary Search Tree | O(log n) | O(n) |

| Insert | Binary Search Tree | O(log n) | O(n) |

| Extract min | Min Heap | O(log n) | O(log n) |

| Sort | Any comparison sort | O(n log n) | O(n²) |

| Sort | Counting/Radix sort | O(n + k) | O(n + k) |


*This article is part of the InterviewHack.ai preparation series — practical resources for developers going through technical interviews at every level.*

FAQ

What topics are covered in a backend developer interview?+

Backend interviews typically cover data structures and algorithms, database design (SQL, indexing, ACID properties), API design (REST, GraphQL, gRPC), system design (caching, message queues, distributed systems), concurrency and async programming, security (SQL injection, XSS, authentication), and language-specific topics like garbage collection, goroutines, or Python's GIL.

What is the N+1 query problem and how do you fix it?+

The N+1 problem occurs when you fetch a list of N records and then make one additional database query per record — N+1 queries total. Fix it by using JOIN-based eager loading, batching with SQL IN clauses, or tools like GraphQL's DataLoader that batch and deduplicate queries per request.

What is the difference between REST, GraphQL, and gRPC?+

REST uses HTTP/1.1 with JSON and is best for public APIs and simple CRUD operations. GraphQL lets clients request exactly the fields they need, eliminating over-fetching — ideal for mobile clients and complex data graphs. gRPC uses HTTP/2 with binary Protocol Buffers for high-performance internal microservice communication.

How do you prevent SQL injection?+

Always use parameterized queries or prepared statements — never interpolate user input directly into SQL strings. ORMs like SQLAlchemy, Hibernate, and Prisma use parameterization by default. Additionally, grant database users only the minimum required privileges (no DROP/ALTER), and use a WAF as an additional layer.

What is eventual consistency and when is it acceptable?+

Eventual consistency means that if no new updates are made, all nodes will eventually converge to the same value — but at any moment, different nodes may return different data. It is acceptable for like counts, view counts, search indexes, and user profile updates. It is not acceptable for bank balances, inventory during checkout, or authentication tokens after logout.

What is the difference between optimistic and pessimistic locking?+

Pessimistic locking acquires a lock before reading (SELECT FOR UPDATE), preventing other transactions from modifying the row. Optimistic locking reads without a lock and validates at write time using a version column — failing if the version changed. Use pessimistic locking for high-contention scenarios; optimistic locking works better for distributed systems and low-contention scenarios.

How do you approach designing a new backend system?+

Start by clarifying requirements: core use case, read/write ratios, expected scale, availability and consistency requirements. Then define the API surface, sketch high-level components (load balancer, API servers, database, cache, queue), design the data model with appropriate indexes, identify and scale bottlenecks, and explicitly discuss the tradeoffs of each major decision.

What is a message queue and when should you use one?+

A message queue enables asynchronous communication between services — the producer sends a message and continues immediately, while the consumer processes it independently. Use message queues to offload slow operations (email, SMS), absorb traffic spikes, fan out events to multiple consumers, handle temporarily unavailable services, and process long-running jobs like video encoding or PDF generation.

Related articles

How to Answer Conflict-With-a-Coworker Interview Questions

Learn how to answer conflict-with-a-coworker interview questions with real examples and proven techniques. Stand out in tech and remote job interviews.

How to Answer 'Why Do You Want to Work Here' in Interviews

Discover expert strategies for answering 'why do you want to work here,' tailored for remote tech roles and dollar opportunities. Real, practical interview tips.

Frontend Developer Interview Questions and How to Answer Them (50+)

Complete SEO article covering 54 frontend developer interview questions with detailed answers, real code snippets across HTML, CSS, JavaScript, React, TypeScript, accessibility, security, build tools, and testing.

Full-Stack Developer Interview Questions: How to Answer Like a Pro (45+)

Comprehensive full-stack developer interview guide with 46 numbered questions covering JavaScript/TypeScript, React, CSS, REST APIs, databases, Node.js, system design, security, testing, DevOps, and advanced architecture topics. Each answer includes working code examples and production-level context.

Prepare for your real interview

Paste your job link: we research who's interviewing you and rehearse you live.

Start free →

Have an interview coming up? Install the live copilot →

InterviewHack.ai

Prepare for the exact interview: who's interviewing you, a tailored CV, and a real coach.

Product

JobsCompanies hiringFree ATS checkerInterview-English checkSalary checkLATAM salary reportFree coursesBlogTailored CVSpoken practiceIt's free

Remote jobs

ReactPythonFull-StackLATAMArgentinaMexicoSee all →

Prepare

Spoken practiceFrontendBackendAI EngineerBy companySell with your CV

Company

For employersAboutContactPrivacyTerms

© 2026 InterviewHack.ai · Your CV is yours. Never used to train anything. · A product of IA-PTY