Tradeoffs

Tradeoffs

20 scenarios — canonical answer and when the opposite is right.

Sorted by interview frequency

You're building a banking ledger with multi-row transactions and complex reports across accounts, transactions, and customers.

ABest pick
PostgreSQL (SQL)
B
DynamoDB (Key-Value NoSQL)
Multi-row ACID transactions + ad-hoc joins/reports are exactly what relational engines exist for. NoSQL forces app-side joins and either denormalization or distributed transactions.
When the opposite is right: If access patterns were a small fixed set keyed by account_id and you needed extreme write throughput, DynamoDB-style design would win.

A user uploads a video that must be transcoded into 5 resolutions before being playable.

A
Process synchronously in the request handler
BBest pick
Enqueue and process asynchronously
Transcoding takes minutes — far exceeding any reasonable HTTP timeout. Queue + worker pool decouples upload from processing and allows retries.
When the opposite is right: For sub-second operations where the caller needs the result immediately, async adds complexity (polling, callbacks) without benefit.

Small team (8 engineers) building a new B2B SaaS product.

ABest pick
Modular monolith
B
Microservices
Microservices add operational complexity (deploy, observability, distributed tx) that an 8-person team rarely has capacity for. Keep boundaries clear in code; split later if scaling demands.
When the opposite is right: When team count grows to where independent deploys and language flexibility become bottlenecks, splitting along domain boundaries pays off.

Read-heavy workload where staleness for a few seconds is tolerable.

ABest pick
Cache-aside
B
Write-through cache
Cache-aside is simpler, the cache only stores what's actually read, and the app stays the source of truth. TTL handles staleness.
When the opposite is right: If reads must always be consistent with the latest write and write volume is moderate, write-through avoids the cache-miss latency spike after updates.

Multiple users may edit the same document, but concurrent edits are rare.

ABest pick
Optimistic locking (version/etag)
B
Pessimistic locking (DB row lock)
When conflicts are rare, optimistic locking avoids the throughput hit of held locks and gracefully surfaces conflicts to the user.
When the opposite is right: Hot rows with frequent contention (e.g., decrementing inventory in a flash sale) benefit from short pessimistic locks to avoid endless retry loops.

You need to fan out user events to ~10 downstream services and let each independently replay the last 7 days for backfill.

ABest pick
Kafka
B
AWS SQS
Kafka's append-only log + consumer groups + offset management is built for multi-subscriber fan-out with replay. SQS is a point-to-point queue; once consumed, messages are gone.
When the opposite is right: For a single worker fleet processing a simple work queue with no replay need, SQS is far simpler and cheaper.

You're designing internal service-to-service APIs across polyglot backends with strict latency SLAs and schema evolution.

A
REST + JSON
BBest pick
gRPC + Protobuf
gRPC has lower latency (HTTP/2 + binary protobuf), enforced schemas via .proto, codegen for many languages, and built-in streaming.
When the opposite is right: For public-facing APIs consumed by browsers and third parties, REST/JSON is the dominant standard with better tooling and discoverability.

A collaborative editor needs sub-100ms updates across users.

A
Long polling
BBest pick
WebSockets
WebSockets give true bidirectional push with low overhead. Long polling adds latency, reconnection cost, and load balancer headaches.
When the opposite is right: If updates are infrequent (every few minutes) and clients are behind hostile proxies, long polling is more reliable and simpler.

Single Postgres instance approaching CPU saturation as traffic grows.

ABest pick
Scale vertically (bigger box)
B
Scale horizontally (more boxes)
Vertical is the simplest first step for relational DBs: no sharding, no app changes. Modern cloud DBs scale to enormous sizes.
When the opposite is right: Once you hit the largest instance available or write-throughput limits, horizontal (read replicas, sharding) becomes necessary.

Designing a fleet of API servers that need to scale horizontally based on traffic.

ABest pick
Stateless servers + external store
B
Stateful servers with sticky sessions
Stateless servers scale and recover trivially: any instance handles any request, autoscaling is straightforward, rolling deploys are safe.
When the opposite is right: Real-time apps (game servers, video conferencing) where state is heavy and latency matters often pin clients to a stateful node.

Sharding 100GB of session data across 10 cache nodes, with occasional node additions/removals.

A
hash(key) % N
BBest pick
Consistent hashing
Modulo remaps ~all keys when N changes. Consistent hashing moves only ~1/N of keys, preventing cache stampedes during scaling.
When the opposite is right: If the node count is truly fixed forever, modulo is simpler and has perfectly uniform distribution.

Mobile clients on flaky networks need to fetch a screen's worth of nested, related data in one round trip.

A
REST
BBest pick
GraphQL
GraphQL lets the client request exactly the fields it needs in one query, eliminating waterfall round trips and over-fetching on slow networks.
When the opposite is right: When response shapes are stable, caching matters (HTTP cache), and team familiarity favors REST, GraphQL's complexity (N+1, auth, query cost) isn't worth it.

Analytics dashboard scanning aggregations over billions of rows but only 3-4 columns at a time.

A
Row-oriented (Postgres)
BBest pick
Columnar (BigQuery/ClickHouse)
Columnar stores read only the queried columns, compress them extremely well, and vectorize aggregations — orders of magnitude faster for analytics.
When the opposite is right: For OLTP workloads with frequent row inserts/updates and lookups by primary key, row stores win on write latency and point reads.

Write-heavy app deployed to two regions for low-latency writes in both.

A
Single leader with cross-region replicas
BBest pick
Multi-leader (active-active)
Single leader forces one region's writes to cross-region — defeating the latency goal. Multi-leader allows local writes; you accept conflict resolution complexity.
When the opposite is right: If consistency outweighs latency (e.g., financial ledger), keep single leader and accept the cross-region write hit.

An order checkout flow spans payment, inventory, and shipping microservices.

A
Two-phase commit
BBest pick
Saga (orchestrated compensations)
2PC blocks on participant failure and is rarely supported across modern services. Sagas embrace eventual consistency with explicit compensation steps.
When the opposite is right: If all participants share one DB engine that supports XA and you need strict atomicity, 2PC remains an option.

You need a cache that also supports rate-limit counters, sorted sets for leaderboards, and pub/sub for notifications.

A
Memcached
BBest pick
Redis
Redis ships rich data structures (sorted sets, hashes, streams) and pub/sub. Memcached is purely string KV.
When the opposite is right: If you genuinely only need an LRU string cache and want maximal memory efficiency + simplicity, Memcached has lower overhead.

Public blog/marketing site that needs SEO and fast first-paint on mobile.

A
Client-side SPA
BBest pick
Server-side rendering (or SSG)
SSR/SSG ships rendered HTML for instant first paint and reliable SEO. Pure SPAs delay both behind a JS bundle download + execution.
When the opposite is right: Highly interactive logged-in apps (dashboards, editors) where SEO doesn't matter often prefer SPAs for richer client-side state.
Related:SD: CDN

Real-time multiplayer game where player positions update 60×/sec.

A
TCP
BBest pick
UDP
Stale position packets are useless — you want the newest, not retransmits of old ones. UDP avoids head-of-line blocking from packet loss.
When the opposite is right: For chat, file transfer, or game inventory updates where every byte must arrive in order, TCP's reliability is required.

User requests account deletion in a system that powers downstream analytics.

ABest pick
Hard delete row
B
Soft delete (deleted_at flag)
GDPR/privacy expectations require actual removal of personal data. Soft delete keeps the data, which becomes a liability.
When the opposite is right: For business documents (invoices, audit records) where you must retain history, soft delete with role-based visibility is correct.

Mid-size startup with shared libraries across 12 services, frequent cross-cutting changes.

ABest pick
Monorepo
B
Polyrepo (one per service)
Atomic cross-service changes, single source of truth for shared libs, unified tooling. Frequent cross-cutting changes are painful across polyrepos.
When the opposite is right: Independent teams shipping totally decoupled services (especially with different release cadences or open-source components) often prefer polyrepo.