Topic 06

Living Below the Ceiling

Theory

The last two topics drew two ceilings: questions no program can answer, and questions every known program answers too slowly for large inputs. Engineers ship anyway. Package managers resolve versions, schedulers pack containers onto machines, and delivery routes are planned every morning, all by changing what they ask for.

And one whole field runs on hardness itself. Every encrypted connection you opened today is safe because some problems are believed to be hard. This topic is the toolkit for working under the ceilings, and then the end of the book: one last look at the spine, and where each chapter's ideas are put to work.

Approximation With a Guarantee

The first move gives up the optimum and keeps a promise. To cover every edge of a graph with as few vertices as possible, repeatedly take an edge that is not yet covered and add both of its ends. The result is never more than twice the size of the smallest cover. Christofides' algorithm of 1976 plans a round trip that is never more than 1.5 times the shortest, for any distances where going direct is never longer than a detour. First-fit decreasing bin packing, met in Chapter 9, sorts the items largest first, drops each into the first bin with room and never uses more than 11/9 of the optimal number of bins plus less than one, about 22% more.

Each of these runs in polynomial time, and each carries a proof of how far from the best it can land. The price is a known, bounded amount of waste, stated before the run. That makes an approximation a planning tool rather than a gamble: you can tell the finance team that a large packing uses at most about 22% more hosts than the best possible one, and be right.

Heuristics Without One

The second move drops the promise too. Greedy choices from Chapter 9, local search that keeps swapping two stops or two jobs while the result improves, and random restarts of that search come with no guarantee about the worst case. They are often excellent on real inputs, and they are what most production systems run.

Kubernetes places a pod by filtering out the nodes that cannot take it and scoring the rest, then picking the best score. It does not solve the packing optimally, and nobody wants it to: a scheduler that searched every placement would never place anything. The catch is that "often excellent" is measured, not proven. A heuristic tuned on last year's workload needs watching on this year's.

Solvers

The third move hands the problem to a general engine. SAT solvers, integer-programming solvers and constraint solvers carry decades of engineering: they learn from each dead end, prune whole regions of the search and on real inputs routinely finish problems that exhaustive search could not touch. You encode the problem, hand it over, and get an answer, or the best answer found so far when a time limit expires.

Package managers are the everyday case. openSUSE's zypper and Fedora's dnf resolve dependencies with libsolv, a SAT-based solver. Newer resolvers, including the one in uv, use PubGrub, a search that learns a new rule from each conflict so it never repeats the same mistake. Solvers cost the work of encoding the problem, and nobody can promise their run time: on the rare input built to be hard, the solver is exponential like everything else.

Changing the Problem

The strongest move is to ask an easier question. Go's module system chose minimal version selection, described by Russ Cox in 2018: take the oldest version of each module that satisfies every stated requirement. That turns an NP-complete resolution into a walk over a graph, linear in the size of the requirements. In exchange, a build never picks up a newer release unless someone asks for it.

The same move works elsewhere. Restrict the input: bounded loops against the halting problem, a small n against exponential search. Accept "probably": a Bloom filter from Chapter 9 answers "definitely not" or "probably yes" in a fraction of the memory. Put a time box on a search and return the best answer found when it expires. Each one trades a property you did not need for a cost you can pay.

Choosing a move for a hard problem
Small instance, exact answer required→Exact search
Need a promise about the waste→Approximation
Large, encodable, can wait a while→Solver with a time limit
Must answer in milliseconds→Heuristic, measured
You own the rules→Change the problem

Hardness as a Feature

Cryptography turns the ceiling into a wall. A 128-bit key has 2 to the 128 possible values. At a billion billion keys a second, trying them all would take about ten trillion years, hundreds of times the age of the universe. RSA rests on the belief that factoring large numbers is slow. That has never been proved, and factoring is not even known to be NP-complete.

Nor would NP-completeness be the right foundation. It is a worst-case statement about the hardest inputs, while a key must be hard to break for typical, randomly chosen keys. Cryptography needs problems that are hard almost all the time, and it rests on assumptions that thousands of researchers have attacked without success, which is a different kind of evidence from a proof. A large quantum computer running Shor's algorithm of 1994 would break RSA and elliptic-curve cryptography, which is why NIST published its first post-quantum standards, FIPS 203, 204 and 205, in August 2024. That is the only line this book spends on quantum computing. The practice of all this is CyberSecurity Deep Dive, Chapter 2, Cryptography in Practice.

The Spine, Once More, and Where to Go Next

Every choice a computer makes has a price, and the price comes from the layer underneath. A dictionary lookup is fast because of a hash table, which is fast because an array index is one multiplication and one memory read, which is fast only when that memory is already in cache. The second read of a file is fast because the kernel kept its pages. Two threads lose an update because adding one is three instructions. At the bottom of every such chain sit the two ceilings of this chapter, and engineering is choosing which price to pay below them.

Each part of the book has a course that puts it to work. The machine, memory, operating system and concurrency chapters continue in Linux Deep Dive: Chapter 6, Processes and Signals, Chapter 13, Performance and Troubleshooting, and Chapter 14, The Kernel and Containers. Reliable delivery continues in Networking Deep Dive, Chapter 5, The Transport Layer. The B-tree continues in PostgreSQL Deep Dive, Chapter 8, Indexes. Exactly-once delivery and its absence continue in Backend Deep Dive, Chapter 7, Failure by Design.

Memory safety and hardness continue in CyberSecurity Deep Dive, in Exploitation Basics in Chapter 5 and in Chapter 2's cryptography. The packing heuristics of this page are the subject of Kubernetes Deep Dive, Chapter 5, Scheduling and Scaling. The language Chapter 13 ran, taught as a language, is Python for Beginners. And many machines working as one, left out of this book on purpose, belong to a System Design course that is on the roadmap and not yet written.

The book's tower, and the course that applies each band
Many machines as one
left out on purpose: a System Design course, on the roadmap
Languages (Ch13)
Python for Beginners
Reliable communication (Ch12)
Networking Deep Dive Ch5 · Backend Deep Dive Ch7
Operating system and concurrency (Ch10-11)
Linux Deep Dive Ch6, Ch13, Ch14
Structures and algorithms (Ch5-9)
PostgreSQL Deep Dive Ch8 · Kubernetes Deep Dive Ch5
The machine and memory (Ch2-4)
Linux Deep Dive Ch13 · CyberSecurity Deep Dive Ch5
Cost and its limits (Ch1, Ch14)
CyberSecurity Deep Dive Ch2: hardness put to work

This book ends where it began, with one question: what does it cost, and why? You now have an answer for every layer from the transistor to the ceiling over all computation. The next time a service is fast in testing and slow in production, you know which layer to look at first.

Misconceptions
  • "An NP-hard problem cannot get a useful answer." Approximations with proven bounds, heuristics and solvers produce good answers every day. What is out of reach is a fast algorithm that is always exactly optimal.
  • "An approximation is only a guess." An approximation algorithm carries a proof: twice the best, 1.5 times the shortest, about 22% more bins at worst. A heuristic is the one without a guarantee.
  • "RSA is secure because factoring is NP-complete." Factoring is not known to be NP-complete, and RSA's security is an unproven assumption. NP-completeness would not help anyway, because it describes the hardest inputs while a key must be hard on typical ones.
  • "Heuristics are hacks that a better engineer would replace with the optimal algorithm." For an NP-hard problem the known optimal algorithm is exhaustive search. The heuristic is the engineering, and the skill is in measuring how good it is.
Why It Matters
  • Reach for an approximation with a proven bound first, a heuristic second and exhaustive search last. The order follows what each one can promise.
  • Hand hard combinatorial problems to a solver with a time limit. A solver plus a budget returns the best answer found instead of hanging.
  • Change the rules when you own them. Minimal version selection shows that a narrower question can remove the hardness entirely.
  • Use vetted cryptographic primitives and never invent your own. Their security rests on assumptions thousands of researchers have attacked, and a new scheme has none of that scrutiny.
RelatedGreedy algorithms the heuristic that sometimes has a bound (Chapter 9)Probabilistic structures trading certainty for space (Chapter 9)P and NP the classification this page works around (see P, NP and Hard Problems)

Knowledge Check

A logistics team must plan 400 delivery stops by 6 a.m. every day and wants a written limit on how much longer the route can be than the best. Which move fits?

  • Exhaustive search, run overnight on a larger cluster
  • An approximation algorithm with a proven ratio
  • A greedy heuristic tuned on last month's routes
  • A rule that caps every van at twenty stops

A vertex cover algorithm is a factor-2 approximation. What does it promise?

  • Its cover is the smallest possible on about half of all inputs
  • It runs in at most twice the time of an exact algorithm
  • Its cover is never more than twice the size of the smallest
  • It finds the best cover with a probability of at least a half

Why is NP-completeness the wrong foundation for a cryptographic scheme?

  • It describes the hardest inputs; a key must be hard on typical ones
  • NP-complete problems can all be solved quickly by modern SAT solvers
  • NP-completeness was proven false for large inputs after 1971
  • NP-complete problems are too slow to use for encrypting any data

What does Go's minimal version selection give up in exchange for making resolution easy?

  • The ability to depend on more than one module at a time
  • Any guarantee that the chosen versions satisfy the requirements
  • Reproducible builds, since versions change on every build
  • Picking up newer releases unless someone asks for them

Roughly how long would trying every 128-bit key take at a billion billion keys per second?

  • About a week on a large enough cluster of modern machines
  • About ten thousand years, well beyond any practical attack
  • About ten trillion years, far longer than the universe's age
  • About a century, which is why key sizes are raised every decade

You got correct