Why-Not Handbook
Overview
Most engineering material explains why an approach works. This handbook answers the opposite, harder question: why not the alternative? Each page takes a pair — or a small set — of options that interviewers, code reviewers, and designers routinely contrast (BFS vs DFS, hashing vs binary search, mutex vs semaphore, B-tree vs LSM, caching vs recomputation) and explains the mechanism behind each option deeply enough that the trade-offs fall out as consequences rather than memorized slogans.
The collection deliberately overlaps with the Trade-offs section, which stores quick-reference comparison tables. There, a one-line table row says “hash: O(1) average, no ordering”. Here, the same comparison is re-derived from first principles: how the hash bucket is probed, what the cache does to that probe, what happens under adversarial keys, and the specific workloads where the binary search that “loses” on paper is the correct engineering answer. If you want a fast answer, read trade-offs; if you want to defend that answer under three rounds of “why?”, read here.
The Six-Step Meta-Framework
Every page in this handbook applies the same six steps, in the same order. The order is the point: each step narrows the space so the final recommendation is a consequence of evidence, not taste.
- Define the problem and its constraints. A comparison without a workload is a slogan. Before comparing, the pages pin down: what the input looks like (size, distribution, mutation rate), what guarantees the caller needs (exact vs approximate, ordering, freshness, worst-case bounds), and what resources are fixed (memory budget, latency budget, hardware). Binary search vs hashing is undecidable until someone says whether range queries are needed and whether keys are adversarial.
- Explain the mechanism of each option. Not “hash tables are O(1)” but why: hash the key, index into a bucket array, resolve collisions by chaining or probing, resize when load factor exceeds a threshold. Mechanism-level explanations expose the constants, the memory behavior, and the failure modes that asymptotic notation hides. Two operations with the same big-O can differ by 50× in practice, and the mechanism tells you which one.
- State complexity, guarantees, and failure modes. For each option: worst-case, average-case, and amortized complexity where they differ; the formal guarantees it gives (ordering, stability, linearizability, bounded latency); and the characteristic ways it breaks (rehash storms, deadlocks, frontier blowups, write skew). Guarantees are contracts — the pages state exactly which contract each option signs.
- Name the conditions where each option wins. Not a generic ranking, but explicit trigger conditions: “DFS wins when the graph is deep and memory is bounded”, “LSM wins when write throughput dominates and reads tolerate milliseconds of staleness”. Every option in every page is the right choice somewhere — the handbook has no strawmen.
- Find the edge cases and counterexamples. This is where most material stops and this handbook starts. Each page includes at least one non-obvious case where the “obvious” choice fails: BFS frontier blowup on lattice graphs, hash-flooding attacks, lost wakeups in condition variables, write skew under snapshot isolation, cache stampedes after an invalidation. These cases are what interviewers use to separate rote answers from real understanding.
- Write the decision framework. Each page ends with a table or ordered checklist mapping workload properties to a choice. The framework is falsifiable: if your workload matches the left column, the right column is the recommendation — and the page has already told you what you are giving up.
The framework generalizes beyond the forty comparisons here. When you meet a fresh “X vs Y” question, running these six steps produces an answer that is specific to your constraints and honest about costs.
File Inventory
| Page | Core question | Flagship edge case covered |
|---|---|---|
| bfs-vs-dfs | Same O(V+E) complexity — why does traversal order change what you can guarantee? | BFS frontier blowup on grid/lattice graphs; recursion depth overflow in DFS |
| binary-search-vs-hashing | O(log n) guaranteed vs O(1) average — which contract do you need? | Hash-flooding adversarial keys; cache-friendly sorted arrays beating hash tables at small n |
| arrays-vs-linked-lists | When is O(1) insertion real, and when is cache locality worth more? | “O(1) insertion” that requires an O(n) search first; reallocation invalidating iterators |
| sorting-algorithms | Five n-log-n-class algorithms — why do libraries ship hybrids? | Naive quicksort’s O(n²) collapse on sorted input; radix sort’s hidden per-pass costs |
| concurrency-primitives | Mutex vs semaphore vs condvar; processes vs threads; polling vs event-driven I/O | Lost wakeup; crash blast radius; busy-polling trading CPU for tail latency |
| database-choices | Normalization vs denormalization; B-tree vs hash vs LSM vs GiST/GIN; optimistic vs pessimistic | Write skew under snapshot isolation; LSM read amplification without bloom filters |
| caching-distributed | Caching vs recomputation; consistency vs availability vs latency | Cache stampede after invalidation; per-operation CAP choices inside one system |
| crash-recovery | WAL vs in-place vs append-only/COW: where does atomicity live on disk? | Torn pages defeat redo deltas — full-page writes and doublewrite exist; append-only’s compaction as slow-motion recovery |
| push-vs-pull | Who owns the pacing — consumer or producer — and what failure does each import? | Retry storm after receiver recovery; slow consumer under push turns broker memory into the failure |
| replication-modes | What exactly does a commit OK promise about the second copy? | MySQL semi-sync silently downgrades to async during the failure it exists for; PG sync stalls indefinitely |
| scaling-up-vs-out | Which part of the workload is serial, and what makes the serial remainder irrelevant? | The global counter hot row sharding cannot fix; Amdahl crossover when coordination grows with node count |
| stateless-vs-stateful | Every system has state — which placement’s failure mode can you afford? | JWT logout that logs out nothing until expiry; per-node rate limiter undercounting by the fleet size |
| static-vs-dynamic-typing | When is type information checked, and which error class does each choice relocate? | TypeScript’s erased boundary — the typed lie at unvalidated data edges; the orthogonal strong/weak axis (Python vs JS vs Java) |
| monolith-vs-microservices | What failure layer do you import when the network becomes a design layer? | The 27× retry cascade through three layers of 3× retries; five engineers operating a distributed monolith |
| rest-vs-grpc | Where does the API contract live — social convention or compiled artifact — and who is the client? | gRPC-Web exists because browsers cannot read trailers; long-lived HTTP/2 channels unbalancing L4 load balancers |
| orm-vs-raw-sql | Who owns the query text, and what does each ownership model hide? | ORM-generated predicates that defeat indexes (function-wrapped columns, implicit casts); identity-map staleness |
| tcp-vs-udp | Where does reliability live — transport or application — and who owns congestion? | TCP’s loss-based congestion control misdiagnosing wireless loss; UDP MTU fragmentation black holes |
| sync-vs-async-io | What does the program do while waiting for the kernel, and who hides the wait? | The async file I/O myth on Linux (epoll reports regular files always-ready); one blocking call freezing an event loop |
| snowflake-vs-uuid | Who generates the ID, what does the B-tree experience, and what do strangers learn? | UUIDv4 index fragmentation multiplied across every secondary index; sharded auto-increment steps that resharding breaks |
| row-vs-column-stores | Which questions is the physical layout cheap at — few rows/all columns or all rows/few columns? | HTAP’s async staleness seam; compaction starvation under chatty updates; “Cassandra is a column store” (data-model vs storage axis) |
| password-hashing-choices | How is verification made deliberately expensive, and what does each hardness buy? | bcrypt’s silent 72-byte truncation (and the pre-hashing zero-byte bug it spawns); the login endpoint as a DoS amplifier |
| graphql-vs-rest | Who shapes the response — and who pays for shape? | Introspection hands attackers the schema map; the request-scoped DataLoader that leaks; one GraphQL round trip slower than three cached REST calls |
| batching-vs-streaming | When does processing start — after a window or on arrival? | The lambda architecture’s dual-truth drift; the micro-batch interval that is three dials in one; the backfill a pure streaming system cannot perform |
| etl-vs-elt | Which engine owns transformation, and does raw data land? | The raw-zone PII masking race; silent schema drift landing loud downstream; streaming CDC that fits neither letter |
| monorepo-vs-multirepo | Is coordination tooling or process — where do contracts break? | The shared package that never reached 2.0 (polyrepo upgrade deadlock); monorepo CI collapse without affected-target computation; access control fragmenting the monorepo anyway |
| canary-vs-blue-green | How is the new version exposed over time, and who notices failure? | Dual-write doubling side effects in the overlap window; sticky sessions poisoning canary statistics; the migration that broke the rollback target |
| virtual-dom-vs-signals | Where is change detection computed — diff, track, or compile? | The memoization treadmill (hand-written dependency graphs); torn reads in naive signal graphs; the 10,000-row list that signals do not save |
| normalize-vs-denormalize | Where does complexity live — the read path or the sync path? | JSON columns as transactionally-synced denormalization (and the planner-statistics trap); the index as the denormalization you already accepted; the join that outgrew a node |
| rpc-vs-message-queue | Who waits for the answer, and who owns the failure — in-line errors vs deferred lag | Request-reply over queues: waiting re-added while every RPC guarantee is removed; the six-hour consumer outage a lag graph failed to page |
| docker-compose-vs-kubernetes | One host’s daemon vs a cluster’s reconciling control plane — what “restart” even means | The scale: 5 that validated horizontal scaling on a single CPU; SIGTERM-and-readiness choreography compose never taught the app |
| app-events-vs-cdc | Who declares what happened — application intent outside the transaction, or the log’s committed truth | The outbox pattern as the bridge (and its retention/ordering edges); the 3 a.m. SQL fix CDC caught and application events missed |
| serverless-vs-containers | Who owns the idle time — per-invocation billing vs provisioned capacity | The websocket gateway billed 100% utilization per connection; cold starts that only fire during incidents; the cron job that crossed the execution ceiling |
| regex-vs-parser | Pattern matching vs grammar parsing — where the nesting boundary sits | Recursive descent triggered at “one more level of nesting”; the silent wrong-match that passes validation; the engine guarantee split (backtracking vs automata) |
| kafka-vs-rabbitmq | What does the broker promise — a replayable log or an ack-and-delete queue? | The prefetch window that flips at-least-once into at-most-once on crash; retention expiry silently deleting un-consumed records |
| elasticsearch-vs-db-fulltext | Who owns the inverted index, and what does that ownership cost? | The GIN pending-list merge cliff; BM25’s shard-local idf skewing relevance on small sharded indexes |
| jwt-vs-sessions | Where does authentication state live, and what does revocation cost? | The denylist that converges back into a session table; clock drift expiring a fleet’s tokens at once |
| vms-vs-containers | Which boundary draws the isolation line — hypervisor or shared kernel? | The shared-kernel fate that turns one syscall 0-day into cross-tenant escape; the pids controller nobody set |
| logical-clocks-lamport-vs-vector | Total order or concurrency detection — what can each clock even see? | The concurrent update Lamport timestamps silently order into a fabricated “last write”; vector pruning that deletes exactly the concurrency it exists to catch |
| optimistic-vs-pessimistic-locking | When does contention pay for coordination up front versus at commit? | The lock held across an HTTP call exhausting the connection pool; the 100-writer commit stampede without backoff |
| sagas-vs-two-phase-commit | Where does uncertainty live while a transaction crosses services? | The coordinator crash that froze the ledger’s in-doubt locks; the compensation that raced an already-shipped order |
Coverage Ledger
The same concepts appear across several research projects from deliberately different angles — the coverage plan requires linking, not re-explaining. The ledger below records which angle each project takes so you know where to go next. (Paths are relative to pages in this directory.)
| Concept | This handbook’s angle | Elsewhere |
|---|---|---|
| Hash tables | Vs binary search: ordering, guarantees, adversarial keys | Follow-up Tree (escalation ladder), Misconceptions (the false O(1) claim), Assumptions Hunt (adversarial inputs) |
| DB index structures | B-tree vs hash vs LSM vs GiST/GIN by workload | Connection Atlas (dependency web), Indexing strategy (practical SQL guide) |
| Mutexes and locking | Ownership semantics and signaling vs exclusion | Follow-up Tree (escalation to lock-free), Misconceptions (threads-equal-speed myth) |
| Isolation and concurrency control | Optimistic vs pessimistic decision rule | Assumptions Hunt (implicit isolation assumptions) |
| Caching and backpressure | Caching vs recomputation; staleness vs availability | Connection Atlas (queue-full path), Cache patterns (operational playbooks) |
| High-level comparison tables | Deep mechanisms here | Trade-offs (quick-reference tables) |
| Crash recovery and WAL | WAL vs in-place vs append-only by failure mechanism | WAL Internals (engine mechanics), Storage Durability (device-level assumptions) |
| Push vs pull | Pacing ownership and where each mode relocates failure | Backpressure: Atlas (the queue-full path), Kafka and Pulsar (pull vs push brokers) |
| Replication modes | What the commit acknowledgment promises | Primary-Backup (topology), GitHub October 2018 (async failover incident) |
| Scaling direction | Amdahl crossover; contention is not partitioning | Amdahl’s Law (the arithmetic), Relational DBs Don’t Scale (the anti-slogan) |
| State placement | Failure-mode and revocation-latency relocation per state type | Leases (ownership primitive), Consensus & Leases: Atlas (safety under partitions) |
| Type systems | Checking time, erasure, and the orthogonal strong/weak axis | Python Typing and TypeScript (the gradual middle path) |
| Architecture style | The network as a failure layer; Conway-aligned team topology | Architecture Trade-offs (quick tables), Microservices (operational catalog) |
| API style (REST vs gRPC) | Contract location, reachability, caching, deadline propagation | REST and gRPC (protocol mechanics), API Contracts (contract assumptions) |
| Query ownership (ORM vs SQL) | Who writes the text the planner sees | Query Planner (plan mechanics), Query Planner Dataflow (text-to-execution web) |
| Transport choice (TCP vs UDP) | Where reliability lives; QUIC as the synthesis | TCP vs UDP (quick tables), QUIC (reliability rebuilt on UDP) |
| I/O model (sync vs async) | Who hides the wait: scheduler, readiness, or completion queues | I/O Multiplexing (syscall mechanics), io_uring (completion-based design) |
| Identifier design (auto-increment vs UUID vs Snowflake) | Generation authority, index locality, leakage | B+Tree (locality mechanics), URL Shortener (ID design as a design interview) |
| Physical layout (row vs column) | Access-shape economics; HTAP as the hybrid answer | Column Stores (layout depth), HTAP (the hybrid architecture) |
| Password hashing (SHA-2 vs bcrypt/scrypt/Argon2) | Cost-per-guess engineering and memory hardness | Security & Crypto (deployment assumptions), Cryptography (the primitives) |
| API style (GraphQL vs REST) | Who shapes the response; caching, query cost, introspection | GraphQL (the query language), REST (the resource model), REST vs gRPC (the contract-location fork) |
| Pipeline pacing (batch vs streaming) | When processing starts: windows vs continuous | Batch Processing and Stream Processing (mechanics), Exactly-Once Semantics (the stream tax) |
| Transform placement (ETL vs ELT) | Which engine transforms; whether raw lands | Snowflake (the elasticity that flipped ELT), Debezium (streaming CDC) |
| Repository strategy (monorepo vs multirepo) | Coordination as tooling vs process | Git Workflows (branching inside either shape), Git Workflow Assumptions (what history promises) |
| Deployment strategy (canary vs blue-green) | Exposure over time; detection vs rollback | Canary Releases (the playbook), Feature Flags (software-layer exposure) |
| UI update model (VDOM vs signals) | Where change detection is computed | React Internals (diff/scheduler mechanics), Svelte (the compiler-driven middle) |
| Physical schema (normalize vs denormalize) | Copies and their sync strategies, uniformly | Denormalization (read-optimization patterns), Incremental View Maintenance (engine-maintained copies) |
| Communication style (RPC vs queues) | Who waits; where failures surface; delivery semantics per side | Message Queues: Follow-up Tree (escalation ladder), Queues and Kafka (broker mechanics) |
| Orchestration level (Compose vs Kubernetes) | Scheduling unit: host daemon vs fleet control plane | Kubernetes Pod Lifecycle: Follow-up Tree (reconciliation mechanics), Deployments (rolling updates) |
| Event declaration (app events vs CDC) | Who owns truth; the dual-write hole and the outbox bridge | Event Sourcing & WAL: Atlas (the log kinship), Debezium (CDC connector mechanics) |
| Execution model (FaaS vs containers) | Who owns idle time; cost curves, cold starts, ceilings | Serverless internals (scale-to-zero mechanics), Firecracker (the microVM substrate) |
| Text structure (regex vs parser) | Where nesting breaks patterns; engine guarantee classes | Regex Engines: Follow-up Tree (engine ladder), Regex Input Assumptions (pattern-encoded assumptions) |
| Message broker choice (Kafka vs RabbitMQ) | What the log-versus-queue mechanism decides about replay, delivery state, and failure | Message Queues: Follow-up Tree (escalation ladder), Kafka and RabbitMQ (broker internals) |
| Full-text search (database vs search engine) | Who owns the inverted index and pays for analysis | Search & Inverted Index: Atlas (dependency web), Full-Text Search (SQL-side mechanics) |
| Authentication state (JWT vs sessions) | Where identity truth lives and what revocation latency costs | Auth, Identity, Session: Atlas (the auth web), JWT is Encrypted (the signature confusion) |
| Isolation boundary (VMs vs containers) | What the hypervisor isolates versus what the kernel partitions | Containers Are VMs (the myth), VM vs Container (quick tables) |
| Time and ordering (Lamport vs vector clocks) | Causality capture versus total order — detection vs serialization | Clocks and Time (physical-time assumptions), Hybrid Logical Clocks (the synthesis) |
| Row concurrency control (optimistic vs pessimistic) | Contention economics: waiting versus wasted work | Database Isolation (isolation assumptions), Transactions & Concurrency Control: Atlas (dependency web) |
| Distributed commit (sagas vs 2PC) | Blocking coordination versus compensating workflows | ACID Transactions: Follow-up Tree (escalation ladder), Event Sourcing & WAL: Atlas (the outbox/log kinship) |
Duplication boundary (from the project plan): trade-offs pages hold the scannable tables; this handbook holds mechanisms, complexity derivations, guarantees, and edge cases. If you find yourself copying a table between the two, link instead.
How to Use This in an Interview
A strong “why not X?” answer has three moves. First, name the mechanism of the rejected option (“a hash table probes one bucket after computing a hash”). Second, state the guarantee you actually need (“I need predecessor queries and ordered iteration”). Third, conclude conditionally, not absolutely (“so for this workload the hash table cannot substitute; for pure point lookups it would be faster”). Pages here give you all three moves pre-assembled, plus the edge case that turns a good answer into a memorable one.
Interview Questions
- Why does the handbook insist on mechanisms before trade-offs? Because every trade-off line item (“O(1) average”, “stable”, “no ordering”) is a consequence of a mechanism. Deriving consequences from mechanisms lets you answer variants you never memorized — new workload, new failure mode, new hardware — while a memorized table breaks the moment the question changes.
- What does “conditions where each wins” prevent? Strawman comparisons. If your explanation of an option cannot name a workload where it is the best choice, you do not understand the option — you have only memorized its weaknesses.
- Give an example of an edge case changing the default choice. On paper, hashing beats binary search for lookups. Under adversarial keys and a known hash function, an attacker forces every key into one bucket and turns O(1) into O(n) — a denial of service. Hardened hash functions (keyed seeds, SipHash) restore the guarantee; alternatively, a sorted array plus binary search gives a hard O(log n) that no input can break. The workload detail — are keys attacker-controlled? — flips the decision.
- What belongs in a decision framework that does not belong in a trade-off table? An ordering. A table lists dimensions; a framework says which question to ask first (“Do you need ordering? If yes, the hash family is out regardless of everything else”). Ordered checklists mirror how real designs narrow down.
- How do the coverage boundaries keep the book maintainable? Same concept, different angles, explicit links. The Why-Not Handbook derives from mechanisms; trade-offs summarizes; Follow-up Tree escalates through interview question chains; Misconceptions attacks false claims. A reader landing on any page gets pointers to the others instead of duplicated prose.
Key Takeaways
- Every comparison here runs the same six steps: constraints → mechanisms → complexity/guarantees/failure modes → winning conditions → edge cases → decision framework. The order matters; the last step is a consequence of the first five.
- Mechanisms expose what asymptotic notation hides: cache behavior, constants, amortization spikes, and the exact contract each data structure or system signs.
- No option is a strawman. Each page states the explicit conditions under which the “losing” option is the correct engineering choice.
- Edge cases — adversarial inputs, frontier blowups, write skew, stampedes — are where defaults fail and where interview depth is proven.
- This collection complements, and links to, the quick-reference tables in Trade-offs rather than duplicating them.
Cross-References
- Engineering Trade-offs — the quick-reference comparison tables this handbook goes deeper than.
- Database Trade-offs — high-level SQL/NoSQL, consistency, and indexing tables referenced by the database pages here.
- DSA — the main DSA track whose chapters (sorting, hashing, graphs) provide the tutorial-level background for the mechanism discussions here.
- Follow-up Tree — question-escalation chains for concepts compared in these pages.
- Assumptions Hunt — the hidden assumptions behind the complexity claims this handbook quotes.
- Connection Atlas — dependency-web view of the same database concepts.
- Misconceptions — the false beliefs this handbook’s edge cases are designed to disarm.