API and Full Stack Coding Patterns Questions
Beyond pure algorithms, be prepared for problems that combine algorithmic thinking with API design, rate limiting, caching strategies, or distributed system concepts. Understanding how a coding solution fits into a larger fullstack architecture.
HardTechnical
70 practiced
You have a cache in front of a write-heavy dataset where a small subset of items are hot and updated frequently. Design an approach to keep cache coherent with writes while minimizing write amplification and latency. Compare cache-aside, write-through, write-back, and invalidation broadcast strategies and propose a practical implementation for strong or eventual consistency.
Sample Answer
Clarify requirements & trade-offs:- Write-heavy dataset, small hot subset updated frequently. Objective: keep cache coherent while minimizing write amplification (extra writes to cache/backing store) and latency. Need to support either strong consistency or eventual consistency depending on API.Compare strategies:1) Cache-aside (lazy load)- How: Writers update DB, then invalidate or delete cache entry; readers load on miss.- Pros: Simple, minimal extra writes when keys cold.- Cons: Race conditions: reader may read stale data between DB write and cache invalidation; requires careful ordering (write DB then invalidate). For hot keys, frequent invalidations cause cache churn and thundering herds.2) Write-through- How: Writers update cache synchronously, cache writes through to DB.- Pros: Readers get up-to-date values from cache (stronger freshness), simple semantics.- Cons: Every write hits cache + DB (write amplification) and increasing write latency (sync to DB). For write-heavy hot subset, this is expensive.3) Write-back (write-behind)- How: Writers update cache; cache asynchronously flushes to DB.- Pros: Low write latency, coalesces multiple updates into fewer DB writes (reduces write amplification).- Cons: Risk of data loss on cache failure; complex eviction and durability semantics; harder to provide strong consistency.4) Invalidation broadcast (pub/sub)- How: Writers update DB then publish invalidation messages to subscribers (cache nodes) to evict/refresh entries.- Pros: Keeps caches coherent with lower bandwidth than writing full payloads; can be efficient for hot keys if only invalidations sent. Supports multiple cache replicas.- Cons: Needs reliable, low-latency messaging; ordering guarantees matter; transient stale reads possible until invalidation applied.Practical implementation patternsA) Strong consistency (API requires read-after-write semantics)- Use a primary-write ordering: client writes go to a coordinator/service that performs write to DB, then synchronously updates cache (write-through) or performs DB write then synchronous invalidation plus fresh cache write.- Prefer a combined operation: update DB, then publish invalidation and wait for ACKs from local cache node(s) or update cache directly from coordinator. To control latency, keep cache local to the coordinator or use a distributed cache with single-writer routing for hot keys.- For hot keys, route all writes through a leader shard for that key (per-key leader) to serialize updates and update cache once—avoids races.- Use optimistic locking/versioning (compare-and-swap or version numbers) so readers see only latest version. Example: DB write returns new version; cache set includes version; readers check version against expected.B) Eventual consistency (allow short staleness, optimize throughput)- Use cache-aside with invalidation broadcast via message bus (Kafka/Redis Streams). Writer writes DB, publishes invalidation event (key + version). Cache nodes subscribe and either evict or refresh lazily.- For hot keys, implement write coalescing and rate-limited refresh: caches hold a short TTL with jitter to avoid thundering herd and use a single-flight loader (only one backend fetch on miss).- Use write-behind for hot keys: update cache immediately, buffer/persist the update to an append-only log, and batch flush to DB periodically (coalesce updates). Ensure durability by writing the update to the log before acknowledging.- Add read-repair: on read detect stale version and trigger background refresh.Hybrid practical proposal (recommended):- Default: cache-aside + invalidation pub/sub for most keys (minimal writes, eventual consistency).- Hot-key path: detect hot keys via metrics; promote those keys to a hot-key service that uses per-key leader routing: - Writes go to leader node which writes to DB and updates cache (write-through on leader or write-back with durable log). - Leader broadcasts versioned updates (key, version, payload optional) to other caches. Use compacted Kafka topic or Redis Pub/Sub with sequence numbers. - Readers consult cache; if version mismatch, they either block briefly to get fresh value (strong mode) or return stale (eventual mode).- Safety: use version numbers, idempotent updates, and durable write-ahead log for write-behind to prevent data loss.- Performance knobs: configurable TTL, coalescing window, single-flight fetch, backpressure on writes, circuit-breaker to bypass cache when DB lag detected.Key operational considerations:- Monitor invalidation lag, message bus latency, and cache hit rate.- Test failure modes: message loss, leader crash—use leader election and replay from compacted log.- Backpressure & flow control to protect DB from hot-key write spikes.This approach balances low latency and reduced write amplification: cold keys pay cache-aside cost; hot keys are optimized by coalescing and routing to minimize redundant DB writes while offering either strong or eventual consistency based on configuration.
HardTechnical
136 practiced
Design server-side logic that ensures exactly-once processing semantics for incoming webhooks that may be retried by senders. Provide Python pseudocode that deduplicates incoming events by a unique event id, persists processing state atomically, handles concurrent retries, and ensures side effects (e.g., DB writes) are executed once. Discuss limitations.
Sample Answer
Approach (brief):- Treat each webhook's unique event_id as an idempotency key. Record its processing state in a durable store with an atomic create-or-get and ensure side effects run only once inside a transaction or coordinated via a lock + durable marker. Use DB unique constraints or Redis SETNX to handle concurrent retries.Pseudocode (Python-style, PostgreSQL + optional Redis for locks):Key concepts / reasoning:- Use a durable events table with unique constraint on event_id to deduplicate concurrent inserts.- Use transactional updates and FOR UPDATE to serialize state reads/writes so only one worker performs side effects.- Optionally use a short Redis lock to reduce DB contention.- Make side effects idempotent where possible (ON CONFLICT DO NOTHING, upserts).- Return 200 quickly to the sender once event is known processed or being processed to avoid retries.Complexity:- DB traffic per event: O(1). Locks and transactions add latency but bound.Edge cases & limitations:- If side effects include external systems (HTTP calls), you must persist intent before calling and use outbox pattern: write intended side-effect to DB inside transaction, commit, then a reliable background worker executes the external call and marks the outbox entry done. This avoids the window where DB commit succeeds but external call fails.- Distributed transactions across multiple systems are hard; prefer outbox + at-least-once delivery from outbox with dedup keys on targets.- Lock/row cleanup and retention: need garbage collection for old events; storage grows.- Long-running processing: holding DB transaction is bad — use status transitions (PROCESSING -> IN_FLIGHT -> PROCESSED) plus heartbeat and claim logic.- Exactly-once end-to-end is impossible without coordination with external actors; this design achieves exactly-once processing on your side, but side effects to external systems require additional patterns (outbox, idempotent receivers) to approach end-to-end exactly-once.
python
import psycopg2
from contextlib import contextmanager
import redis
import json
r = redis.Redis()
@contextmanager
def db_txn(conn):
cur = conn.cursor()
try:
yield cur
conn.commit()
except:
conn.rollback()
raise
def handle_webhook(conn, payload):
event_id = payload['event_id']
# Fast-path: try Redis lock to serialize concurrent handlers (optional)
lock_key = f"lock:event:{event_id}"
have_lock = r.set(lock_key, "1", nx=True, ex=30)
if not have_lock:
# another worker is processing; either wait or immediately return 200 to sender
return 200
try:
with db_txn(conn) as cur:
# Try to insert a row for this event. Unique constraint on event_id.
try:
cur.execute(
"INSERT INTO webhook_events(event_id, status, payload) VALUES (%s, %s, %s)",
(event_id, 'PROCESSING', json.dumps(payload))
)
# We "own" processing
owned = True
except psycopg2.IntegrityError:
# row exists; check status
conn.rollback() # clear error state
cur.execute("SELECT status FROM webhook_events WHERE event_id=%s FOR UPDATE", (event_id,))
row = cur.fetchone()
if row is None:
# shouldn't happen
return 500
status = row[0]
if status == 'PROCESSED':
return 200 # already done
elif status == 'PROCESSING':
# concurrent processing ongoing; decide to wait, poll, or return success
return 200
else:
# retryable state: claim by updating status (optimistic)
cur.execute("UPDATE webhook_events SET status=%s WHERE event_id=%s", ('PROCESSING', event_id))
owned = True
# perform side effects (idempotent design recommended)
perform_side_effects(cur, payload)
# mark processed atomically
cur.execute("UPDATE webhook_events SET status=%s WHERE event_id=%s", ('PROCESSED', event_id))
return 200
finally:
if have_lock:
r.delete(lock_key)
def perform_side_effects(cur, payload):
# All DB writes happen using the same DB transaction/cursor so they are atomic with the status update.
cur.execute("INSERT INTO orders(id, user_id, amount) VALUES (%s,%s,%s) ON CONFLICT DO NOTHING",
(payload['order_id'], payload['user_id'], payload['amount']))
# other writes...MediumTechnical
69 practiced
Explain the GraphQL N+1 database query problem using an example where a client requests a list of posts and for each the author details. Describe how batching and a DataLoader-like approach solves the problem and sketch a per-request batching implementation idea.
Sample Answer
GraphQL N+1 problem: imagine a query that asks for posts and for each post the author:{ posts { id title author { id name } } }If you fetch posts (1 query) and then for each of N posts you run a separate DB query to load its author, you perform 1 + N queries — slow when N is large.Why batching helps: instead of N separate author queries, collect all unique author IDs from the resolver calls and fetch them in a single IN (...) query. That reduces 1 + N to 2 queries (posts + authors) and avoids repeated work.DataLoader-like approach (concept):- Per request, maintain a loader that: - Queues requested keys (author IDs) as resolver functions run. - De-duplicates keys. - On a dispatch trigger (end of tick or explicit flush), issues one batched DB call to fetch all keys and resolves all queued promises with results. - Optionally caches results for the duration of the request.Sketch (JavaScript-style pseudocode):Key trade-offs:- Per-request scope prevents cross-request data leakage and keeps cache small.- Batching window length (next tick vs microtime) balances latency vs aggregation.- Remember to dedupe keys and handle missing rows gracefully.
javascript
class RequestLoader {
constructor(batchFn) {
this.queue = new Map(); // id -> [resolvers]
this.scheduled = false;
this.batchFn = batchFn;
}
load(id) {
return new Promise(resolve => {
if (!this.queue.has(id)) this.queue.set(id, []);
this.queue.get(id).push(resolve);
if (!this.scheduled) {
this.scheduled = true;
process.nextTick(() => this.flush());
}
});
}
async flush() {
const ids = Array.from(this.queue.keys());
const resolversMap = this.queue;
this.queue = new Map();
this.scheduled = false;
const results = await this.batchFn(ids); // single DB query
for (const id of ids) {
const value = results[id] || null;
for (const resolve of resolversMap.get(id)) resolve(value);
}
}
}MediumTechnical
76 practiced
Implement a POST /payments endpoint in Java (Spring preferred) that accepts an Idempotency-Key header and payment payload. Provide code or pseudocode that atomically checks and records idempotency keys, returns correct HTTP responses on duplicate requests, supports TTL eviction, and explains concurrency control and rollback handling.
Sample Answer
Approach: Use a database-backed idempotency table with a unique constraint on (idempotency_key, client_id) and a status + response storage. Use a transactional “insert-if-not-exists” (optimistic) to atomically claim a key. Store creation timestamp for TTL eviction (periodic job). If an entry exists and is completed, return stored response; if in-progress, wait/poll or return 409/202 depending on policy. Use DB transactions to ensure atomicity and rollback payment processing on failure.Code (Spring, JPA + pseudo payment service):Key components:- IdempotencyRepo.claim(key, clientId): attempts single-row insert with unique constraint inside a DB transaction; on duplicate key throw IdempotencyConflictException.- IdempotencyEntry: fields (idempotencyKey, clientId, status {IN_PROGRESS,COMPLETED,FAILED}, responseBody, httpStatus, createdAt).- TTL eviction: scheduled job or DB TTL index that deletes entries older than TTL (e.g., 24h). Alternatively, background worker deletes WHERE createdAt < now() - interval.Concurrency & rollback:- Atomic claim via DB unique constraint prevents multiple processors from running payment twice.- Payment processing happens after marking IN_PROGRESS; if payment fails, update status=FAILED so retries are allowed or client informed.- Use DB transactions for each DB save. If paymentService includes external side effects (charging card), ensure idempotent provider calls (pass idempotency key to gateway) or implement compensating actions on failure.Edge cases:- Long-running in-progress requests: choose polling/backoff or return 202 with status endpoint.- Multi-instance race: DB unique constraint ensures correctness.- Storage of large responses: limit size or store blobs.Time complexity: O(1) DB ops per request. This pattern is robust for production payment endpoints.
java
@RestController
@RequestMapping("/payments")
public class PaymentsController {
private final IdempotencyRepo idemRepo;
private final PaymentService paymentService;
private final ObjectMapper mapper = new ObjectMapper();
public PaymentsController(IdempotencyRepo idemRepo, PaymentService paymentService){
this.idemRepo = idemRepo;
this.paymentService = paymentService;
}
@PostMapping
public ResponseEntity<?> createPayment(@RequestHeader("Idempotency-Key") String key,
@RequestBody PaymentRequest req) {
String clientId = req.getClientId(); // or from auth
try {
// Try to create a claim row atomically
IdempotencyEntry entry = idemRepo.claim(key, clientId);
if(entry.getStatus() == Status.COMPLETED){
// Return stored response
return ResponseEntity.status(entry.getHttpStatus())
.body(mapper.readValue(entry.getResponseBody(), Object.class));
}
if(entry.getStatus() == Status.IN_PROGRESS){
// Another request is processing. Respond with 409 or 202
return ResponseEntity.status(HttpStatus.CONFLICT).body("Request in progress");
}
// Mark in-progress and process payment within a transaction
entry.setStatus(Status.IN_PROGRESS);
idemRepo.save(entry);
PaymentResult result;
try {
result = paymentService.process(req); // external call; may throw
} catch(Exception ex) {
// mark failed so client can retry
entry.setStatus(Status.FAILED);
entry.setResponseBody(ex.getMessage());
entry.setHttpStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
idemRepo.save(entry);
throw ex;
}
// on success, persist response for future duplicates
String respBody = mapper.writeValueAsString(result);
entry.setStatus(Status.COMPLETED);
entry.setResponseBody(respBody);
entry.setHttpStatus(HttpStatus.CREATED.value());
idemRepo.save(entry);
return ResponseEntity.status(HttpStatus.CREATED).body(result);
} catch(IdempotencyConflictException cex){
// insert-if-not-exists lost due to race; fetch existing
IdempotencyEntry existing = idemRepo.findByKeyAndClient(key, clientId)
.orElseThrow(() -> new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR));
if(existing.getStatus() == Status.COMPLETED){
return ResponseEntity.status(existing.getHttpStatus()).body(existing.getResponseBody());
}
return ResponseEntity.status(HttpStatus.CONFLICT).body("Request in progress");
}
}
}HardSystem Design
134 practiced
Design a resilient webhook delivery platform that delivers events to third-party endpoints. Include delivery guarantees (at-least-once), retry policies with exponential backoff and jitter, dead-letter queue handling, batching to improve throughput, per-subscriber rate limits, secret management, monitoring, and a retry dashboard for subscribers.
Sample Answer
Requirements:- Functional: accept events, deliver to subscriber HTTP endpoints with at-least-once semantics, support batching, per-subscriber rate limits, secret-based signing.- Non-functional: high throughput (millions/day), low latency, multi-region resilience, observability (metrics, logs, tracing), admin + subscriber retry dashboard.High-level architecture:API Ingest -> Event Store (Kafka) -> Dispatcher service(s) -> Rate limiter -> Batcher -> Delivery workers -> DLQ store -> Retry controller -> Secret ManagerMonitoring + Dashboard + AlertingComponents:1. Ingest API: validates, authenticates producers, attaches event metadata, writes to partitioned Kafka topic keyed by subscriber id for ordering guarantees.2. Dispatcher: reads events per subscriber partition, enforces per-subscriber rate limits (token-bucket via Redis or local leaky-bucket with centralized tokens), groups events into batches (configurable size/time window).3. Delivery Worker: sends HTTP requests with HMAC signature from Secret Manager (secrets stored in KMS + cached in secure vault). Uses async HTTP client with connection pooling, follows exponential backoff with full jitter (sleep = random_between(0, base * 2^attempt)), caps max backoff, and circuit-breaker per endpoint to avoid cascading failures.4. Retry Controller & DLQ: delivery failures (network 5xx, connection errors, or non-retriable codes depending on subscriber config) get retried until max attempts or TTL. After exhaustion, events go to per-subscriber DLQ (durable DB or Kafka topic). Subscribers can requeue from DLQ via dashboard or API.5. Idempotency & At-least-once: include event-id and attempt-id; subscribers should handle dedup. To reduce duplicates, maintain short-lived delivery cache (Redis) to avoid immediate duplicate deliveries across retries.6. Batching: batch size/time trade-off; use adaptive batching based on endpoint latency to maximize throughput while respecting rate limits.7. Secret Management: store signing keys in KMS (AWS KMS/GCP KMS) and rotate keys; delivery service fetches keys with least privilege, caches and auto-refreshes.8. Monitoring & Dashboard: Prometheus metrics (success rate, latencies, retries, DLQ counts), distributed tracing (OpenTelemetry), logs (structured). Subscriber-facing dashboard shows delivery history, retry attempts, failure reasons, allow manual retry/replay, update rate-limit or webhook config.9. Scalability & Resilience: Dispatcher & workers stateless and autoscaled. Kafka partitions scale with subscribers. Use multiple regions with active-active or active-passive failover and geo-routing.10. Trade-offs: At-least-once simplifies guarantees but pushes dedup responsibility to subscribers. Strong ordering per-subscriber requires single-partition consumption which limits parallelism per subscriber — mitigate by per-topic-per-subscription sharding for high-volume subscribers.Edge cases:- Slow endpoints: circuit-breaker + backpressure to avoid queue buildup; move high-volume endpoints to dedicated partitions.- Bursts: token-bucket smoothing and DLQ overflow throttling.- Secret compromise: immediate key rotation and revoke, mark subscriber endpoints disabled.This design provides resilient at-least-once delivery, robust retries with jitter, manageable DLQ workflows, throughput via batching, per-subscriber rate control, secure secret handling, and full observability plus a subscriber retry dashboard.
Unlock Full Question Bank
Get access to hundreds of API and Full Stack Coding Patterns interview questions and detailed answers.
Sign in to ContinueJoin thousands of developers preparing for their dream job.