Skip to content

Backend Interview Guide — APIs, DB & Architecture

DodaTech Updated 2026-06-21 5 min read

In this tutorial, you'll learn about Backend Interview Guide. We cover key concepts, practical examples, and best practices to help you understand and apply this topic effectively.

Backend interviews test API design, database architecture, Distributed Systems, scalability strategies, and operational excellence for building server-side applications at scale. These interviews focus on tradeoff analysis — every design decision has pros and cons, and interviewers want to see your reasoning. Durga Antivirus Pro processes millions of threat intelligence requests daily — every backend decision impacts performance and reliability.

Learning Path

flowchart LR
  A[Coding Interview Prep] --> B[Backend Interview]
  B --> C[System Design]
  C --> D[Distributed Systems]
  D --> E[Behavioral Prep]
  style B fill:#f90,color:#fff

API Design

RESTful API Patterns

# Good REST design
GET    /api/users             # List users
POST   /api/users             # Create user
GET    /api/users/:id         # Get user
PUT    /api/users/:id         # Replace user
PATCH  /api/users/:id         # Partial update
DELETE /api/users/:id         # Delete user
GET    /api/users/:id/orders  # User's orders

Pagination

# Cursor-based (preferred for large datasets)
GET /api/users?cursor=abc123&limit=20

# Response
{
  "data": [...],
  "next_cursor": "def456",
  "has_more": true
}

# Offset-based (simpler, less reliable)
GET /api/users?page=2&limit=20

Rate Limiting — Token Bucket

import time

class TokenBucket:
    def __init__(self, capacity, refill_rate):
        self.capacity = capacity
        self.tokens = capacity
        self.refill_rate = refill_rate
        self.last_refill = time.time()

    def allow_request(self):
        now = time.time()
        elapsed = now - self.last_refill
        self.tokens = min(self.capacity, self.tokens + elapsed * self.refill_rate)
        self.last_refill = now
        if self.tokens >= 1:
            self.tokens -= 1
            return True
        return False

limiter = TokenBucket(10, 1)
for _ in range(12):
    print(limiter.allow_request(), end=" ")

Expected output:

True True True True True True True True True True False False

Database Design

SQL vs NoSQL

Factor SQL NoSQL
Schema Fixed, predefined Flexible, dynamic
Relationships Foreign keys, JOINs Embedded docs, references
Consistency ACID guaranteed Eventual (BASE)
Scaling Vertical (usually) Horizontal (built-in)
Best for Structured data, transactions Unstructured data, high write volume

Indexing

-- Single column index
CREATE INDEX idx_users_email ON users(email);

-- Composite index — column order matters
CREATE INDEX idx_users_status_created ON users(status, created_at);

-- Uses the composite index
SELECT * FROM users WHERE status = 'active' AND created_at > '2024-01-01';

-- Cannot fully use the composite index
SELECT * FROM users WHERE created_at > '2024-01-01' AND status = 'active';

N+1 Query Problem

# BAD: N+1 queries
users = User.objects.all()
for user in users:
    print(user.profile.bio)  # Hits DB once per user

# GOOD: Eager loading
users = User.objects.select_related('profile').all()
for user in users:
    print(user.profile.bio)  # One query with JOIN

Scalability

Caching Strategies

# Cache-aside pattern
def get_user(user_id):
    user = cache.get(f"user:{user_id}")
    if user:
        return user
    user = db.query(User).filter_by(id=user_id).first()
    cache.set(f"user:{user_id}", user, ttl=3600)
    return user

# Write-through cache
def update_user(user_id, data):
    db.query(User).filter_by(id=user_id).update(data)
    db.commit()
    cache.set(f"user:{user_id}", data, ttl=3600)

Distributed Systems

CAP Theorem

Consistency (C) — Every read returns the most recent write
Availability (A) — Every request gets a response
Partition Tolerance (P) — System works despite network failures

CP systems: Traditional databases (consistency over availability)
AP systems: DNS, CDN (availability over consistency)

Consistent Hashing

class ConsistentHashRing:
    def __init__(self, nodes, replicas=3):
        self.replicas = replicas
        self.ring = {}
        self.sorted_keys = []
        for node in nodes:
            self.add_node(node)

    def add_node(self, node):
        for i in range(self.replicas):
            key = hash(f"{node}:{i}")
            self.ring[key] = node
            self.sorted_keys.append(key)
        self.sorted_keys.sort()

    def get_node(self, key):
        if not self.ring:
            return None
        hash_key = hash(key)
        for skey in self.sorted_keys:
            if hash_key <= skey:
                return self.ring[skey]
        return self.ring[self.sorted_keys[0]]

ring = ConsistentHashRing(["cache-1", "cache-2", "cache-3"])
print(ring.get_node("user:123"))
print(ring.get_node("session:456"))

Microservices Communication

# Synchronous (HTTP) — simple but creates coupling
def create_order(user_id, items):
    user = requests.get(f"http://user-service/users/{user_id}")
    payment = requests.post(f"http://payment-service/charges",
                            json={"user": user_id, "amount": total})
    return {"order_id": order_id, "status": "created"}

# Asynchronous (Message Queue) — decoupled, resilient
def create_order_async(user_id, items):
    message = {"user_id": user_id, "items": items}
    queue.publish("orders.create", message)
    return {"status": "processing"}

Common Mistakes

  1. Ignoring database design — Focusing only on app logic without discussing schema, indexing, and query patterns.
  2. Not discussing tradeoffs — Every decision has tradeoffs. Ignoring them signals inexperience. "I'd use NoSQL for flexibility, but we lose JOINs."
  3. Over-engineering — Kubernetes and Event Sourcing for a simple CRUD app. Start simple, evolve.
  4. Ignoring security — Not mentioning auth, input validation, or rate limiting. Always discuss security.
  5. No observability — Designing the system but not mentioning how you'd monitor it.
  6. Forgetting error handling — Assuming everything always works. Discuss retry, circuit breakers, graceful degradation.
  7. Tight coupling — Services depending on each other's internal data structures. Communicate through well-defined APIs.

Practice Questions

1. REST vs GraphQL? REST has fixed endpoints returning fixed data. GraphQL has one endpoint where clients specify needs. GraphQL reduces over-fetching but complicates caching.

2. When NoSQL over SQL? Flexible schemas, horizontal scaling, high write throughput, unstructured data. SQL for complex queries, transactions, clear relationships.

3. What is the N+1 query problem? Loading a list of entities then loading related entities one-by-one. Fix with eager loading (JOINs) or batch loading (DataLoader).

4. Explain CAP theorem. Distributed Systems can guarantee only two of three: Consistency, Availability, Partition Tolerance. Choose based on your requirements.

5. Challenge: Design a URL shortener covering API design, database schema, caching, rate limiting, analytics tracking, and scaling. Discuss tradeoffs at each decision.

Real-World Task

Take an existing API you use (GitHub, Stripe, OpenWeather) and build a backend service that wraps it with caching, rate limiting, authentication, and your own clean RESTful interface.

FAQ

{{< faq "How important is System Design for backend interviews?">}} Very important — it's typically 1–2 rounds, especially for mid-to-senior positions. Study load balancing, caching, database sharding, message queues, and CAP theorem. {{< /faq >}}

What databases should I know?

Be comfortable with at least one SQL database (PostgreSQL or MySQL) and one NoSQL database (MongoDB or Redis). PostgreSQL is the most versatile choice for interviews.

What's the most important distributed systems concept?

The CAP theorem and its practical implications. Almost every distributed system question traces back to CAP tradeoffs between consistency, availability, and partition tolerance.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro