You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
What experienced backend engineers forget before an interview, grouped by subtopic. For PostgreSQL internals see postgresql.md; for Redis see redis.md; for queues, sagas, and retries see distributed.md; for caching and rate limiting see system.md; for HTTP versions see networking.md; for auth and vulnerabilities see security.md.
missing or invalid credentials (re-auth may help) vs refused — credentials, if sent, are insufficient; others might succeed (or 404 to hide that the resource exists)
Keyset: WHERE (created_at, id) > (:c, :id) ORDER BY ... LIMIT n — fast at any depth and unaffected by inserts; needs a unique, immutable sort key, or updated rows skip or repeat.
Expose the position as an opaque cursor; a cursor is just a token and can also encode an offset.
Offset: simple and jumps to page N, but slows with depth and skips or repeats rows as data changes.
Versioning
URI (/v1/) is explicit and cacheable; a header (Accept: ...;version=2) keeps URLs clean but is harder to discover.
Whatever the scheme: additive changes only within a version; removing or renaming a field is a breaking change.
Write the business row and an outbox row in the same DB transaction; a relay (poller or CDC) publishes outbox rows and marks them sent — the transactional outbox.
Fixes the dual-write problem without 2PC, which most brokers don't support. Delivery is at-least-once (see distributed.md).
Inbox deduplication
Consumers record processed message IDs in the same transaction as their effect and skip repeats — the receiving side of the outbox (idempotent consumer).
Expire entries only after the broker's maximum redelivery window.
Service calls
Timeouts and deadline propagation
Every outbound call gets a timeout, shorter than the caller's own.
Propagate the deadline so downstream services stop work once the original caller has given up — gRPC sends it on the wire, but each server must pass it on to its outbound calls and honor cancellation.
Retries, backoff, and circuit breakers: see distributed.md.
Protobuf: never reuse or renumber a field tag (mark removed ones reserved); adding fields is safe; proto3 has no required; unknown fields survive a binary round trip, but not conversion to JSON.
Avro with a schema registry checks BACKWARD, FORWARD, or FULL compatibility on every schema change.
JSON consumers must ignore unknown fields (tolerant reader); rename a field by adding the new one, writing both, moving readers over, then removing the old.
Webhooks
Sign each payload (HMAC over timestamp + body) and have receivers verify it with a constant-time compare; reject stale timestamps to stop replays.
Deliver at least once with backoff, so receivers dedupe by event ID; receivers should ack fast (2xx) and process asynchronously.
Fetching a user-supplied webhook URL is an SSRF risk — see security.md.
Async work
Long-running operations
Return 202 Accepted with a Location to a status resource (/operations/123); the client polls it or gets a webhook when done.
The status resource carries state (pending, running, succeeded, failed) and the result or error (asynchronous request-reply).
Background jobs
At-least-once delivery means jobs must be idempotent; persistently failing jobs go to a dead-letter queue with alerting.
Give every job a timeout; long jobs checkpoint progress so a retry resumes instead of restarting.
Scheduled jobs
Guard against overlapping runs, where one run lasts longer than the interval.
With several replicas, each fires the same cron — use a lock (DB row, advisory lock, lease) or a single scheduler.
Data access
N+1 queries
Loading a list, then one query per item. Fix with a JOIN, a batched WHERE id IN (...), or a DataLoader that batches lookups per tick (GraphQL resolvers).
Spot it with per-request query counts in logs or APM.
Connection pool sizing
Size the connection pool from the database's capacity, not the app's thread count — many app instances × a large pool overwhelm the database.
A good starting point is a small pool per instance (~2× DB cores total across instances, HikariCP's formula), then measure wait time; server-side pooling in postgresql.md.
Transaction boundaries
Never make a network call inside an open DB transaction — it holds locks and a connection for the whole round trip.
For "commit, then notify another service", use the outbox instead of calling inside the transaction.
Multi-tenancy models
Shared tables with a tenant_id: cheapest, but every query must filter — enforce it with PostgreSQL row-level security (the table owner bypasses it unless FORCE ROW LEVEL SECURITY).
Schema per tenant: more isolation, but migrations and connection pools multiply with the tenant count.
Database per tenant: strongest isolation, per-tenant restore and data residency, the most operational work.
Move noisy neighbors (the largest tenants) to their own shard or database, and rate-limit per tenant.
Time handling
Store and send instants in UTC (RFC 3339 with an offset, 2026-03-08T09:30:00Z); in PostgreSQL use timestamptz.
For future local-time events (meetings, schedules), store the local time plus an IANA zone (Europe/Berlin), not an offset — DST rules and offsets change.
A DST switch skips or repeats a local hour, so a job scheduled at 02:30 local time runs never or twice; schedule jobs in UTC.
File uploads
Clients upload straight to object storage with a presigned URL, bypassing app servers (system.md).
Multipart or resumable uploads retry individual parts instead of the whole file.
Service lifecycle
Graceful shutdown
On SIGTERM: fail readiness → stop accepting new connections → finish in-flight requests within a deadline → close pools and flush telemetry → exit.
Workers stop pulling jobs and finish or checkpoint the current one. Kubernetes timing: see devops.md.
Health endpoints
Separate endpoints: liveness checks only that the process can make progress; readiness checks what serving needs (warm-up done, required dependencies) and fails while draining.
Keep them cheap and on an internal port; probe configuration and pitfalls: see devops.md.
Backend security
Mass assignment
Binding the request body straight onto a model lets clients set isAdmin: true (mass assignment) — allowlist bindable fields or use separate DTOs.
Serialize responses through explicit DTOs too, so a new sensitive column doesn't leak.
Input validation
Validate type, format, and range at the boundary before business logic (400); business-rule failures come later (409/422).
Cap sizes — body, array lengths, string lengths, nesting depth — to prevent resource exhaustion.
Secrets in logs
Request logging middleware can capture tokens, auth headers, and PII — redact centrally in the logging layer, not per call site (OWASP logging).
Tokens in URL query strings end up in proxy and CDN logs — send them in headers.
Testing
Integration tests with real dependencies
Testcontainers runs real dependencies (Postgres, Kafka) in tests, catching SQL and serialization bugs that mocks hide.
A mock only checks your assumptions about a dependency; test the real one at least at the adapter boundary.