Sweaty Imposter

Tradeoffs

58 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)
Why PostgreSQL (SQL) wins

Multi-row atomicity, consistency, isolation, durability (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.

Read the Full Page

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
Why Enqueue and process asynchronously wins

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.

Read the Full Page

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

ABest Pick
Modular monolith
B
Microservices
Why Modular monolith wins

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 the Full Page

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

ABest Pick
Cache-aside
B
Write-through cache
Why Cache-aside wins

Cache-aside is simpler, the cache only stores what's actually read, and the app stays the source of truth. Time-to-live (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.

Read the Full Page

Multiple users may edit the same record, but concurrent edits are rare. You're choosing the concurrency-control model.

ABest Pick
Optimistic locking (read a version or ETag, update only if it still matches)
B
Pessimistic locking (hold a row lock for the whole edit)
Why Optimistic locking (read a version or ETag, update only if it still matches) wins

When conflicts are rare, optimistic locking lets writers proceed without blocking, retries only the unlucky few, and surfaces the conflict to the user. Pessimistic locks serialize every editor and tie up connections for slow, human-paced edits.

When the Opposite Is Right

Hot rows where conflicts are the norm, like decrementing inventory in a flash sale, turn optimistic locking into a retry storm. Short pessimistic locks (SELECT ... FOR UPDATE) are cheaper there.

Read the Full Page

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
Why Kafka wins

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.

Read the Full Page

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

A
REST + JSON
BBest Pick
gRPC + Protobuf
Why gRPC + Protobuf wins

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.

Read the Full Page

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

A
Long polling
BBest Pick
WebSockets
Why WebSockets wins

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.

Read the Full Page

Single Postgres instance approaching CPU saturation as traffic grows.

ABest Pick
Scale vertically (bigger box)
B
Scale horizontally (more boxes)
Why Scale vertically (bigger box) wins

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.

Read the Full Page

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
Why Stateless servers + external store wins

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.

Read the Full Page

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

A
hash(key) % N
BBest Pick
Consistent hashing
Why Consistent hashing wins

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.

Read the Full Page

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
Why GraphQL wins

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.

Read the Full Page

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)
Why Columnar (BigQuery/ClickHouse) wins

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 online transaction processing (OLTP) workloads with frequent row inserts/updates and lookups by primary key, row stores win on write latency and point reads.

Read the Full Page

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)
Why Multi-leader (active-active) wins

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.

Read the Full Page

An order checkout spans cart, payment, inventory, and shipping services, each with its own database.

A
Two-phase commit across the services
BBest Pick
Saga with compensating actions
Why Saga with compensating actions wins

Two-phase commit (2PC) holds locks in every participant until the coordinator decides, blocks if the coordinator fails mid-protocol, and couples independently deployed services and teams. A saga runs each step as a local transaction and undoes completed steps with compensations, so services stay autonomous at the cost of eventual consistency.

When the Opposite Is Right

Inside one trusted boundary, such as shards of one database engine (Spanner) or one app using MySQL XA, 2PC is workable and gives true atomicity. Sagas trade that atomicity for availability and independence.

Read the Full Page

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

A
Memcached
BBest Pick
Redis
Why Redis wins

Redis ships rich data structures (sorted sets, hashes, streams) and pub/sub. Memcached is purely string key-value (KV).

When the Opposite Is Right

If you genuinely only need a least recently used (LRU) string cache and want maximal memory efficiency + simplicity, Memcached has lower overhead.

Read the Full Page

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

A
Client-side SPA
BBest Pick
Server-side rendering (or SSG)
Why Server-side rendering (or SSG) wins

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
Read the Full Page

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

A
TCP
BBest Pick
UDP
Why UDP wins

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 reliability is required.

Read the Full Page

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

ABest Pick
Hard delete row
B
Soft delete (deleted_at flag)
Why Hard delete row wins

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.

Read the Full Page

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

ABest Pick
Monorepo
B
Polyrepo (one per service)
Why Monorepo wins

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.

Read the Full Page

A URL shortener counts every click for its analytics dashboard, and owners can edit or expire a link's destination. Which status code should GET /:code return?

A
301 Moved Permanently
BBest Pick
302 Found (or 307 Temporary Redirect)
Why 302 Found (or 307 Temporary Redirect) wins

Browsers cache a 301 and go straight to the destination on repeat visits, so those clicks never reach your click stream and a later edit to the link never takes effect for that browser. A 302 sends every click back through the service (or a CDN whose edge logs you collect), which keeps counts accurate and destinations editable.

When the Opposite Is Right

If links never change and you don't count clicks, a 301 saves the extra round trip on repeat visits and takes load off the redirect service.

Read the Full Page

A hot counter is hit by 64 threads thousands of times per second. You need correctness without crushing throughput.

A
Guard the counter with a mutex (`ReentrantLock`)
BBest Pick
Use a lock-free `AtomicLong` with CAS
Why Use a lock-free `AtomicLong` with CAS wins

For single-word updates, CAS avoids the syscall and context-switch cost of mutex contention and scales linearly with CPU count up to the point where CAS retries dominate. Mutexes win once the critical section grows to multiple statements.

When the Opposite Is Right

If the critical section is several reads and writes that must be atomic together, a mutex is simpler and faster than a hand-rolled CAS loop.

Read the Full Page

A service makes ~5 downstream HTTP calls per request, 80% of request time is waiting on I/O, and you expect 50k concurrent connections.

A
Thread-per-request with a large pool
BBest Pick
Async / event-loop with futures
Why Async / event-loop with futures wins

I/O-bound workloads with high concurrency don't need a thread per in-flight call, the threads just sleep on epoll. An event loop drives tens of thousands of concurrent I/Os with a handful of OS threads, cutting memory and context-switch overhead.

When the Opposite Is Right

For CPU-bound work, threads (one per core) win, async buys nothing when nobody is sleeping.

Read the Full Page

You're locking a 1M-entry in-memory map. One big lock is simple but throughput is poor under writes.

A
One global lock around the whole map
BBest Pick
Striped locks (e.g., 16 segments, each with its own lock)
Why Striped locks (e.g., 16 segments, each with its own lock) wins

Striped / fine-grained locks let unrelated keys be updated concurrently, under uniform keys you get roughly N-way write parallelism. This is exactly how Java's pre-Java-8 `ConcurrentHashMap` worked.

When the Opposite Is Right

If operations frequently touch many keys (`putAll`, atomic multi-key updates) a single lock is simpler and avoids deadlock from acquiring multiple stripes.

Read the Full Page

You're building a stateful service with many internal components mutating shared state. You want to keep it correct as it grows.

A
Shared memory + locks across components
BBest Pick
Actors / message-passing, each component owns its state
Why Actors / message-passing, each component owns its state wins

Message passing eliminates whole categories of bugs (deadlock, race, torn read) by removing sharing, each actor is single-threaded inside. Scales to distributed systems trivially because the model is location-transparent.

When the Opposite Is Right

Tight, low-latency loops over shared in-memory state (an in-process cache, a matching engine), locks plus careful design beat message-copy overhead.

Read the Full Page

Your service issues many DB and HTTP calls per request. You're choosing the I/O style for the platform.

A
Synchronous I/O with a large thread pool
BBest Pick
Asynchronous non-blocking I/O with futures/await
Why Asynchronous non-blocking I/O with futures/await wins

Non-blocking I/O scales connection count and concurrency far past what threads can, Netty / Tokio / asyncio routinely drive 100k connections per box. Threads cost ~1MB of stack each and context-switch overhead grows with the pool.

When the Opposite Is Right

Simple internal tools or batch jobs with low concurrency, sync code is dramatically easier to reason about and debug. The cognitive tax of async isn't worth it under low load.

Read the Full Page

Shopping cart service for a global e-commerce site. Users add items from mobile, web, and tablet; some sessions are offline.

A
Strong consistency (single-region Raft)
BBest Pick
Eventual consistency (multi-master + CRDT merge)
Why Eventual consistency (multi-master + CRDT merge) wins

Cart UX tolerates 'union of all adds' semantics, and users hate write failures. The original Dynamo paper picked exactly this trade for Amazon's cart. CRDTs make merges deterministic.

When the Opposite Is Right

For an inventory decrement or a bank balance, strong consistency wins: silently merging two debits is worse than rejecting one.

Read the Full Page

Primary Postgres in us-east-1 replicating to a us-west-2 standby for DR. Writes are user-facing checkout transactions.

A
Synchronous replication (commit waits for standby ack)
BBest Pick
Asynchronous replication (commit on primary, replicate after)
Why Asynchronous replication (commit on primary, replicate after) wins

Cross-region sync replication adds ~70ms to every commit, killing checkout latency. Async accepts a small RPO (recovery point objective) window of unreplicated writes for big latency wins.

When the Opposite Is Right

Financial ledgers with zero-data-loss requirements pick sync (or quorum sync to 2 of 3 standbys). Spanner's TrueTime-based sync replication is the gold standard.

Read the Full Page

Write-heavy time-series workload, 100k writes/sec, can tolerate eventual reads, must stay available during single-AZ outages.

A
Leader-follower (Postgres, MySQL, Raft KV)
BBest Pick
Leaderless (Cassandra, DynamoDB, Riak)
Why Leaderless (Cassandra, DynamoDB, Riak) wins

Leaderless gives write availability during partitions and scales writes horizontally without re-electing a leader. Quorum tuning controls consistency per op.

When the Opposite Is Right

If writes must be linearizable and you need cross-row transactions, leader-based (or Spanner-style sharded Paxos) is the right pick.

Read the Full Page

A user-profile service with N=3 replicas per key, light contention, multi-region.

ABest Pick
Quorum writes (W=2)
B
Single-master writes
Why Quorum writes (W=2) wins

Quorum survives a replica down without failover; single-master needs leader election (seconds of unavailability) on every primary loss. For multi-region, quorum sidesteps the WAN-pinned leader.

When the Opposite Is Right

If you need linearizable reads and cross-key transactions, a single master (or Raft group) is much simpler than gluing quorum + version vectors + repair.

Read the Full Page

Tracking which of ~1500 worker nodes are alive in a batch-processing cluster.

A
Raft consensus over membership list
BBest Pick
Gossip / SWIM-based membership
Why Gossip / SWIM-based membership wins

Raft membership churn at 1500 nodes saturates the leader; you do not need linearizable liveness, just convergence within seconds. SWIM gives sub-second failure detection at constant per-node cost.

When the Opposite Is Right

For a small, security-critical control plane (10s of nodes) where 'who is in the cluster' must be strongly consistent and auditable, Raft membership wins.

Read the Full Page

Storage engine for a write-heavy time-series ingest service. Reads are mostly recent data; durability matters; SSDs.

A
B-tree / B+tree storage (Postgres, InnoDB style)
BBest Pick
LSM tree (RocksDB, Cassandra style)
Why LSM tree (RocksDB, Cassandra style) wins

LSM converts random writes into sequential SSTable flushes plus background compaction, which is a much better fit for write-heavy ingest. Reads of recent data hit the memtable and L0, so read amplification stays modest.

When the Opposite Is Right

Mixed OLTP with heavy in-place updates to the same rows and strict read latency SLOs favor B-trees. LSM read amp and compaction stalls become liabilities.

Read the Full Page

User-profile service joins users, preferences, and subscription rows on almost every read. Writes are infrequent.

A
Normalized schema with joins at read time
BBest Pick
Denormalized profile document
Why Denormalized profile document wins

Read-heavy, write-light workloads pay the denormalization cost (extra storage, update fan-out) only on rare writes, and amortize it across many cheap single-row reads. Joins disappear from the hot path.

When the Opposite Is Right

Write-heavy systems with many places to update on a single change (think e-commerce inventory) usually keep normalization to avoid update anomalies and write amplification.

Read the Full Page

OLTP service mostly does single-row updates with the occasional multi-row consistency check (e.g., 'don't double-book a seat').

ABest Pick
READ COMMITTED isolation
B
SERIALIZABLE isolation
Why READ COMMITTED isolation wins

READ COMMITTED is the cheapest correct default for the common case. Pay for SERIALIZABLE (or explicit row locks / unique constraints) only on the few transactions that actually need protection from write skew or phantoms.

When the Opposite Is Right

If multi-row invariants are pervasive (financial postings, scheduling), running everything at SERIALIZABLE (Postgres SSI) is simpler than reasoning about which transactions need SELECT FOR UPDATE.

Read the Full Page

A reporting team wants to run hour-long aggregate queries against the same Postgres serving your checkout flow.

A
Run analytics on the OLTP database directly
BBest Pick
Move analytics to a separate OLAP store (columnar warehouse, replica with column store extension, Snowflake/BigQuery)
Why Move analytics to a separate OLAP store (columnar warehouse, replica with column store extension, Snowflake/BigQuery) wins

OLTP engines are tuned for short row-oriented transactions; long scans contend with the buffer cache, pollute the plan cache, and (under MVCC) hold back the xmin horizon, bloating tables. OLAP stores are columnar, vectorized, and built for this.

When the Opposite Is Right

Small scale or early-stage products can absolutely run reports on a read replica; introducing a warehouse before you need one is over-engineering.

Read the Full Page

Choosing between Postgres-style heap tables and MySQL/InnoDB-style index-organized tables (clustered on PK) for a new schema.

A
Heap-organized table with separate indexes
BBest Pick
Index-organized table clustered by primary key
Why Index-organized table clustered by primary key wins

When most reads are by PK or PK-range, clustering rows in PK order makes the leaf pages the rows. Range scans are sequential and cache-friendly, and the PK index has zero extra indirection.

When the Opposite Is Right

Workloads dominated by secondary-index lookups suffer in IOT, because secondary indexes store the PK (not a row pointer) and need a second lookup. Heap tables with multiple covering secondary indexes win there.

Read the Full Page

Postgres `synchronous_commit`: leave it ON (fsync WAL before ack) or set it OFF (ack early, fsync later) to chase throughput.

ABest Pick
synchronous_commit = on (fsync before ack)
B
synchronous_commit = off (ack before fsync)
Why synchronous_commit = on (fsync before ack) wins

Default ON is the only setting that guarantees a committed transaction survives a crash. The right way to claw back throughput is batching (group commit), faster storage, or SSDs with battery-backed cache, not silently losing the last N transactions.

When the Opposite Is Right

Workloads where the last few seconds of writes are truly disposable (telemetry, analytics ingest with replay) can flip it off for big throughput gains. Be explicit about the data-loss window.

Read the Full Page

Serving a global audience over flaky mobile networks. Pick HTTP 2 or HTTP/3 at the edge.

A
HTTP/2 over TCP
BBest Pick
HTTP/3 over QUIC
Why HTTP/3 over QUIC wins

QUIC streams are independent at the transport layer, so a single dropped packet does not stall every concurrent stream like it does on TCP. QUIC also has 0-RTT resumption and connection migration across IP changes (Wi-Fi to LTE).

When the Opposite Is Right

Inside the data center on reliable low-loss links, HTTP/2 over TCP is simpler, more debuggable, and friendlier to middleboxes. UDP is also more likely to be rate-limited by corporate firewalls.

Read the Full Page

Returning clients are hitting your API. Do you allow TLS 1.3 0-RTT or require a full 1-RTT handshake?

A
Allow 0-RTT for resumed sessions
BBest Pick
Require 1-RTT (no early data)
Why Require 1-RTT (no early data) wins

0-RTT early data is replayable by an attacker that captures it. For mutations, payments, or anything non-idempotent, that is unsafe. The 1 extra round trip is worth the safety.

When the Opposite Is Right

For idempotent GETs on a latency-critical read path (search, feed, static APIs), enable 0-RTT and bound the replay window with an anti-replay cache.

Read the Full Page

Server expected to hold 100k concurrent long-lived connections. Thread-per-connection or event loop?

A
Thread-per-connection
BBest Pick
Event loop on epoll / kqueue
Why Event loop on epoll / kqueue wins

Threads cost ~1MB of stack and a kernel-scheduled context each; 100k threads is 100GB of stack and ruinous context-switch overhead. Event loops watch many fds from one (or N=cores) threads with O(1) ready notification.

When the Opposite Is Right

For short CPU-heavy requests with few concurrent connections, thread-per-request is easier to reason about and lets blocking calls (sync DB drivers, CPU work) stay on their own thread. Java's virtual threads (Loom) blur the line further.

Read the Full Page

Running untrusted plugin code or risky native libraries. Process or thread?

ABest Pick
Separate process
B
Separate thread in the same process
Why Separate process wins

Processes have separate address spaces; a segfault, memory corruption, or OOM kills only that process. Threads share memory, so one bad pointer takes down the whole server. Chrome and modern browsers run each tab in its own process for this reason.

When the Opposite Is Right

If you need cheap shared memory and frequent communication (e.g., a worker pool over a hot in-memory cache), threads avoid IPC overhead and fork cost. Trust boundary matters more than performance.

Read the Full Page

Database is doing its own page cache and you do not want the kernel double-buffering. Buffered or direct I/O?

A
Buffered I/O (page cache)
BBest Pick
Direct I/O (O_DIRECT)
Why Direct I/O (O_DIRECT) wins

When the app already manages its own buffer pool (Postgres, MySQL, RocksDB, ScyllaDB), the kernel page cache is wasted RAM and double work. O_DIRECT bypasses it and gives the DB deterministic control over what is cached.

When the Opposite Is Right

For file servers, log shippers, or apps that benefit from OS read-ahead and free shared caching across processes, buffered I/O is faster and simpler. O_DIRECT requires aligned buffers and is unforgiving.

Read the Full Page

Load balancing across backends where request durations vary wildly (some 1ms, some 5s). Round robin or least connections?

A
Round robin
BBest Pick
Least connections
Why Least connections wins

Round robin assumes equal-cost requests. With heavy-tailed durations it piles slow requests on whichever backend got unlucky. Least-connections routes new work to the least-loaded backend, smoothing tail latency.

When the Opposite Is Right

For uniform, short, stateless requests with sticky-session needs, round robin (or consistent hashing) is cheaper and avoids the cross-LB connection-count bookkeeping. Least-connections also gets tricky across multiple LB instances.

Read the Full Page

Thirty product teams each maintain their own continuous integration (CI) pipelines, Terraform modules, and Kubernetes manifests, and you have budget for six platform engineers. A base image upgrade takes a full quarter because every team does it separately.

ABest Pick
Central Platform Team
B
Embedded Platform Engineers
Why Central Platform Team wins

A central team should own one shared set of pipelines, modules, and base images, because the waste here is thirty copies of the same work. Spread across thirty teams, six engineers become part-time helpers who each patch a local fork, and the duplication stays. A central team ships the upgrade once and turns adoption into a version bump.

When the Opposite Is Right

When teams' infrastructure needs genuinely diverge, such as a machine learning team running GPU fleets beside web teams, embedding specialists where the unique work lives pays off more than a shared layer.

Read the Full Page

You're moving a billing system that processes $2M a day off a 12-year-old monolith onto new services. It exposes 40 endpoints, and finance allows at most one hour of billing downtime per year.

ABest Pick
Strangler Migration
B
Big-Bang Cutover
Why Strangler Migration wins

Route traffic through a facade and move one endpoint at a time, so each piece proves itself under real load and can roll back on its own. A big-bang cutover puts all 40 endpoints and every undocumented edge case into one weekend, and a single miss spends the downtime budget for the year. The strangler path also delivers value from the first endpoint moved.

When the Opposite Is Right

When the old and new systems cannot run side by side, such as a vendor contract that ends on a fixed date or a data model that can't be dual-written, a rehearsed big-bang cutover is the safer plan.

Read the Full Page

Sixty engineers ship a web app weekly from one repository. A pricing redesign will take three teams six weeks, and customers can't see it until finance signs off.

ABest Pick
Feature Flags
B
Long-Lived Release Branches
Why Feature Flags wins

Merge the redesign to trunk daily behind a flag, so it integrates continuously and launches with a config change once finance approves. A six-week branch shared by three teams drifts from a trunk that sixty people are changing, and the final merge becomes its own project with its own bugs. Flags also allow a staged rollout and an instant off switch.

When the Opposite Is Right

When customers install your software and you patch old versions for years, such as on-prem releases or firmware, long-lived release branches are the standard way to maintain each supported version.

Read the Full Page

Your 200 engineers need on-call scheduling, paging, and incident timelines. Building it would take three engineers two quarters, and a vendor quotes $60,000 a year.

A
Build In-House
BBest Pick
Buy a Vendor Tool
Why Buy a Vendor Tool wins

Paging is undifferentiated work that vendors have hardened across thousands of customers, so the license is the cheaper and safer choice. Three engineers for two quarters costs several times the annual fee before counting years of maintenance, and a homegrown pager that fails during an outage is the worst failure it could have. Those engineers are worth more on the product customers pay for.

When the Opposite Is Right

When the tool is core to your differentiation or no vendor meets a hard constraint, such as air-gapped deployment or data residency, building is worth the ongoing cost.

Read the Full Page

A revenue-critical Rails app has 400,000 lines, 30% test coverage, and twelve engineers. Leadership expects new features every sprint while the code is cleaned up.

A
Full Rewrite
BBest Pick
Incremental Refactoring
Why Incremental Refactoring wins

Refactor in place, adding tests around each area before changing it, so the product keeps shipping and every improvement reaches production. A rewrite has to rediscover years of edge cases from code with little test coverage, and it freezes features for a year or more while the old system keeps changing underneath it. Rewrites of this size routinely run long and launch with regressions.

When the Opposite Is Right

When the platform itself is the problem, such as an unsupported runtime or a framework that can't meet a new requirement, a scoped rewrite of the affected part beats patching a dead end.

Read the Full Page

You're extracting three services from a monolith over the next two quarters. They read and write 80 tables tied together by foreign keys and cross-table transactions.

ABest Pick
Shared Database
B
Database per Service
Why Shared Database wins

Keep the shared database during the extraction and give each table a single owning service that alone may write to it. Splitting 80 entangled tables at the same time as the code doubles the migration risk and replaces transactions with distributed workflows before the boundaries are proven. Once write ownership is clean, each service's tables can move out one at a time with change data capture (CDC).

When the Opposite Is Right

When one service has distinct scale, availability, or compliance needs, such as payment card data with its own audit scope, give it a separate database early, even mid-migration.

Read the Full Page

You're moving 40 teams to a new observability stack that cuts logging costs in half. The old stack still works, and every team is mid-roadmap.

ABest Pick
Paved Road Defaults
B
Top-Down Mandates
Why Paved Road Defaults wins

Make the new stack the default in service templates and do the migration work for teams, so adopting it is easier than staying put. Mandates on working systems produce resentment, exceptions, and half-finished migrations while teams protect their roadmaps. Track adoption, and save a hard deadline for the last few holdouts once most teams have moved.

When the Opposite Is Right

When a straggler means a security or compliance failure, such as an audit deadline or a vulnerable library, a mandate with a fixed date is the right call.

Read the Full Page

A central site reliability engineering (SRE) team of eight takes every page for 150 services owned by 25 teams. Mean time to recovery (MTTR) has doubled in a year, and most incidents trace back to recent deploys.

A
Central SRE Team
BBest Pick
You Build It, You Run It
Why You Build It, You Run It wins

Move first-line on-call to the owning teams, because the people who shipped the change are the fastest to diagnose and fix it. Eight SREs cannot hold context on 150 services, and teams that never get paged have little reason to invest in reliability. Keep the SRE team as the source of tooling, standards, and support on the largest incidents.

When the Opposite Is Right

When a small set of systems needs deep operational expertise, such as shared databases or the core network, a dedicated SRE team owning them outright works better.

Read the Full Page

Checkout calls nine downstream domains synchronously, including fulfillment, loyalty, fraud, and analytics, and new consumers arrive every quarter. An outage in any one of them currently fails checkout.

A
Synchronous APIs
BBest Pick
Domain Events
Why Domain Events wins

Publish an OrderPlaced event as the contract and let each domain consume it at its own pace. Checkout's availability then stops depending on nine other teams, and a new consumer subscribes without any change to checkout. Version the event schema with the same care as a public API, because every consumer depends on it.

When the Opposite Is Right

When checkout needs an answer before it can proceed, such as a fraud decision or an inventory reservation, keep that call synchronous and use events for everything downstream.

Read the Full Page

A 2,000-engineer company has 40 product domains and a central data team of 25 with a nine-month backlog of pipeline requests. Most domains already have engineers maintaining unofficial pipelines of their own.

A
Central Data Platform
BBest Pick
Data Mesh
Why Data Mesh wins

Give domains ownership of their data products and turn the central team into the platform that makes publishing them easy. Twenty-five people cannot learn 40 domains well enough to model their data, which is why the backlog keeps growing and shadow pipelines keep appearing. The central team still sets shared standards for schemas, quality checks, and access control.

When the Opposite Is Right

When the company has a handful of domains, or few engineers with data skills outside one team, a central data platform is faster and more consistent.

Read the Full Page

Three hundred backend engineers work across five languages. A platform team of ten maintains build tooling and software development kits (SDKs) for all five, and on-call engineers often can't read the services they get paged for.

ABest Pick
One Standard Language
B
Team-Chosen Languages
Why One Standard Language wins

Standardize new services on one primary language and approve exceptions case by case, so the shared tooling bill stops multiplying by five. Every extra language needs its own build tooling, SDKs, security patching, and experts, and that cost lands on the platform team and on-call. One language also lets engineers move between teams and read any service during an incident.

When the Opposite Is Right

When a domain has needs the standard language serves poorly, such as data science in Python or low-latency systems in Rust or C++, approve that language for that domain.

Read the Full Page

Your B2B SaaS runs 4,000 tenants on one shared stack, and twice this year a bad deploy or one noisy tenant took every customer down. Enterprise contracts promise 99.95% availability.

ABest Pick
Cell-Based Architecture
B
Shared Multi-Tenant Stack
Why Cell-Based Architecture wins

Split the fleet into cells, each a full copy of the stack serving a fixed slice of tenants, so any failure reaches only one cell. Deploys roll out cell by cell, and a noisy tenant can exhaust only its own cell's capacity. The cost is a routing layer, more fleets to operate, and tooling to move tenants between cells.

When the Opposite Is Right

When outages have been rare and tenants are small, a shared stack with per-tenant rate limits and staged deploys gives most of the protection at a fraction of the operating cost.

Read the Full Page

After a regional outage at your only cloud provider, the board asks for multi-cloud. You have 400 engineers, deep use of managed services, and no regulatory requirement for a second provider.

A
Multi-Cloud
BBest Pick
Single Cloud, Multi-Region
Why Single Cloud, Multi-Region wins

Spread across multiple regions of the provider you already use, which addresses the outage that actually happened at a fraction of the cost. Multi-cloud means trading managed services for lowest-common-denominator tooling or building everything twice, and it doubles the expertise every team needs. Regional failures are far more common than a whole provider going down.

When the Opposite Is Right

When a regulator requires an exit plan, or large customers require you to run in their cloud, a second provider becomes a business requirement worth the cost.

Read the Full Page

Your event streaming moves 3 GB per second, the managed service bill is $4M a year and growing 60% annually, and you already employ six engineers who run streaming infrastructure.

A
Managed Vendor Service
BBest Pick
Self-Hosted Open Source
Why Self-Hosted Open Source wins

Run the open source system yourself, because at this volume the vendor's margin exceeds the cost of the team you already have. Six experienced engineers can operate large clusters, and owning the stack removes the pricing leverage the vendor holds at every renewal. Plan it as a staged migration with dual writes, and budget for the on-call load it adds.

When the Opposite Is Right

When usage is modest or no one on staff has run the system at scale, the managed service is cheaper once you count salaries, on-call, and the outages a new team will cause.

Read the Full Page

Security requires mutual TLS (mTLS) and uniform retry and timeout policies across 900 services written in six languages. The last change to the shared client libraries took nine months to reach every service.

A
Shared Client Libraries
BBest Pick
Service Mesh Sidecars
Why Service Mesh Sidecars wins

Move mTLS, retries, and timeouts into a service mesh, so the platform team rolls out a policy change once without waiting on 900 deploys. Six language-specific libraries drift apart in behavior, and each upgrade depends on every team rebuilding and shipping. The mesh adds latency, resource cost, and a critical control plane to operate, which this scale justifies.

When the Opposite Is Right

With a few languages and dozens of services that deploy often, a well-maintained shared library is simpler, cheaper, and easier to debug than a mesh.

Read the Full Page