Time-Constructible Functions

The time hierarchy theorem promises something almost too clean: give a Turing machine asymptotically more time and it decides strictly more languages. But read the theorem's fine print and you keep meeting the same three-word hypothesis — "time-constructible". Drop it and the theorem is not merely harder to prove; it becomes false. There are (bizarre) time bounds for which squaring, cubing, even exponentiating your whole budget buys you no new power at all.

So what is this condition, and why does the whole edifice of hierarchy theorems rest on it? The short answer: a diagonalising machine must be able to count out its own time budget — to build an alarm clock it can afford to wind. A function that lets it do so is time-constructible. This page defines the notion precisely, shows that every honest running time you will ever name qualifies, and exhibits the pathological monsters that do not — the ones the gap theorem weaponises.

The definition

Intuitively, t(n) is time-constructible if a machine can compute the number t(n) — or equivalently, mark off t(n) tape cells — without spending more than a constant factor over t(n) steps doing it. The clock must be affordable: winding it up may not cost more than the time it is meant to measure.

A function t : \mathbb{N} \to \mathbb{N} with t(n) \ge n is time-constructible if some deterministic Turing machine, given the input 1^n (the number n in unary), does either of these equivalent jobs in O(t(n)) steps:

The two formulations coincide up to the model's usual constant factors, and it is the second — a block of t(n) cells — that the hierarchy proof actually consumes. The companion notion for space, space-constructible, asks the same of memory: a machine that lays out s(n) tape cells using O(s(n)) space, powering the space hierarchy theorem in exactly the same way.

The clock is what makes diagonalisation finite

Recall the diagonal machine \mathrm{Diag} at the heart of the hierarchy proof. On input w = \langle M\rangle it simulates M and then does the opposite of whatever M answers. But a raw simulation of an arbitrary machine can run forever — that is precisely the halting problem. The only thing standing between "separation theorem" and "undecidability" is a stopwatch: simulate for at most t(|w|) steps, and if the budget runs out, stop and decide by default.

To enforce "at most t(|w|) steps", the machine must know that number — and building it must be cheap, or the clock itself would blow the budget it is trying to police. Marking off t(n) cells and decrementing one per simulated step is the mechanism; time-constructibility is exactly the guarantee that the mechanism fits inside O(t(n)). Watch the clock get built and then counted down:

First the input 1^n arrives; then the constructible clock lays out a block of t(n) cells at a cost of O(t(n)); then the diagonaliser burns one clock cell per simulated step of M. When the cells run out, time is up — the simulation halts, and \mathrm{Diag} stays decidable. Remove constructibility and the machine cannot even tell when to stop.

Everything you'd call a running time qualifies

The reassuring news is that the class of time-constructible functions is enormous. Every function you would ever write down as an honest complexity bound is time-constructible:

Function t(n)Constructible?Why (sketch)
nyescopy the input; O(n)
n^2yesmultiply n\times n in O(n^2)
n^3,\; n^kyesrepeated multiplication
n\log nyescompute \lceil\log n\rceil, then a product
2^nyeswrite a 1 followed by n zeros; O(2^n)
n!yesiterated multiplication, well within O(n!)
a wild, uncomputable-jump gnothe machine can't even name g(n) in time

The closure properties help too: sums, products, and compositions of constructible functions are constructible, and any t(n) \ge n\log n that you can compute in O(t(n)) time is in the club. Only pathologically irregular functions — ones that leap around unpredictably, faster than any machine can track within their own budget — are excluded. They never arise as the running time of an actual algorithm, which is why the hypothesis feels invisible in practice.

Counting out the budget, in code

Here is the "clock" made concrete. For each candidate bound we compute t(n) and then simulate laying out and burning down the counter, tallying the work. The tally never exceeds a constant times t(n) — that is constructibility in action. A binary down-counter from t(n) to 0 does O(t(n)) total bit-flips (amortised O(1) per tick), so the clock is genuinely affordable.

// A time bound as a function of n, plus a human-readable name. interface Bound { name: string; t: (n: number) => number; } const bounds: Bound[] = [ { name: "n", t: (n) => n }, { name: "n^2", t: (n) => n * n }, { name: "n log n",t: (n) => Math.ceil(n * Math.log2(n)) }, { name: "2^n", t: (n) => 2 ** n }, ]; // "Wind up and burn down" a binary counter of size B: total bit-flips is O(B). function counterWork(B: number): number { let flips = 0; for (let v = B; v > 0; v--) { // decrementing flips the trailing run of set bits + one more: amortised O(1). let x = v; do { flips++; } while ((x & 1) === 0 && (x >>= 1) > 0); } return flips; } console.log("n | t(n) | clock work | work / t(n) (must stay O(1))"); for (const n of [4, 6, 8, 10]) { for (const b of bounds) { const T = b.t(n); const w = counterWork(T); const ratio = (w / T).toFixed(2); console.log(`${n} | ${String(T).padStart(8)} | ${String(w).padStart(10)} | ${ratio} (${b.name})`); } } console.log("\nEvery ratio is bounded by a constant — each clock costs O(t(n)) to run."); console.log("So n, n^2, n log n and 2^n are all time-constructible.");

The monsters: non-constructible bounds and the gap theorem

Why insist on constructibility at all — surely "more time = more power" is just obvious? The stunning answer is that without it, the intuition is wrong. Trakhtenbrot and Borodin's gap theorem builds a (necessarily non-constructible) time bound with a vast dead zone above it, where an enormous increase in budget decides not one extra language.

The trick is that f is engineered to skip over every "interesting" running-time value — it lands only in a wasteland where no machine's runtime falls between f(n) and g(f(n)). Such an f cannot be time-constructible (if it were, the hierarchy theorem would contradict the gap). This is the sharpest possible statement of why the hierarchy theorem needs the hypothesis: constructibility is exactly the property that rules the gap monsters out. The theorem is not being fussy — its hypothesis is doing indispensable work.

Because "budget increase" and "extra languages decided" are only linked when you can build a clock to spend the budget precisely. The hierarchy theorem separates \mathrm{DTIME}(o(f)) from \mathrm{DTIME}(f\log f) by a diagonal machine that meters exactly f steps — impossible if f is not constructible. The gap theorem lives in that impossibility: it hand-picks an f so ill-behaved that no problem has a runtime in the whole interval [f,\,2^{f}], so widening the budget across that empty interval adds no deciders. The moral: the resource is time, but the currency is a constructible clock. No clock, no purchase.

Constructible is more than computable

A subtle but crucial distinction: time-constructibility is not the same as "the function is computable". A non-constructible f in the gap theorem is perfectly computable — you can write a program that outputs f(n). What fails is the budget: you cannot compute f(n) within O(f(n)) steps. Constructibility is computability with a self-imposed deadline, and that deadline is the entire point. It is what lets a machine measure its own time without a meta-clock, avoiding an infinite regress.

Two traps snare newcomers. First, do not read "time-constructible" as merely "the running time is a reasonable function" — the definition has teeth: the machine must produce t(n) (or mark off t(n) cells) within O(t(n)) steps. A function you can only compute in, say, 2^{t(n)} time is computable but not time-constructible, and it is exactly such functions the gap theorem exploits. Second, do not confuse the roles: the diagonal machine's constructible clock is a tool inside the proof, not a hypothesis about the languages being separated. And note the mild floor — the standard definition assumes t(n) \ge n (often t(n) \ge n\log n), since a machine cannot even read all of an n-bit input, let alone diagonalise, in sub-linear time. State the notion without the budget clause and you have said nothing that keeps the hierarchy theorem true.