Backend Interview Guide — APIs, DB & Architecture
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
- Ignoring database design — Focusing only on app logic without discussing schema, indexing, and query patterns.
- Not discussing tradeoffs — Every decision has tradeoffs. Ignoring them signals inexperience. "I'd use NoSQL for flexibility, but we lose JOINs."
- Over-engineering — Kubernetes and Event Sourcing for a simple CRUD app. Start simple, evolve.
- Ignoring security — Not mentioning auth, input validation, or rate limiting. Always discuss security.
- No observability — Designing the system but not mentioning how you'd monitor it.
- Forgetting error handling — Assuming everything always works. Discuss retry, circuit breakers, graceful degradation.
- 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 >}}
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro