skip to content

questions

20

A service truncates each key's digest to 32 bits as a fingerprint; why are collisions eventually unavoidable?

level: juniorimportance: must knowfreq 70%

answer

  1. count the values, not the function
  2. the target space is finite
  3. no one-to-one map into fewer slots
  4. k bits gives 2^k fingerprints
  5. forced at 2^k plus one distinct keys

basics

~20 s

A 32-bit fingerprint has only 2^32 possible values, so any 2^32 + 1 distinct keys must contain two that share one. That is the pigeonhole principle; a better function cannot avoid it, only a wider field delays it.

solid answer

~40 s

Fingerprinting maps keys into a finite set of values, and 32 bits gives exactly 2^32 = 4,294,967,296 of them. The pigeonhole principle says no one-to-one map exists from a larger finite set into a smaller one, so once the number of distinct keys passes 2^32, at least two of them land on the same fingerprint. Nothing about the function changes this: uniformity, cryptographic strength and cost all affect *where* collisions land and how hard they are to produce deliberately, but none of them adds values to a 32-bit field. The practical consequence is that a fingerprint match is a filter, never a decision — confirm it with a full comparison of the underlying keys. Widening the field raises the threshold, since each extra bit doubles the space, but never removes it.

go deeper

for a junior

Recall the shape of the argument: k bits means 2^k possible values, and more distinct keys than values forces a repeat. Name the pigeonhole principle and give the threshold for the width in the question.

for a middle

Explain that the conclusion rests on the two counts alone, so no property of the function alters it, and that each extra bit doubles the value space and therefore doubles the threshold.

for a senior

Show the operational consequence: a fingerprint match gates a full comparison, deduplication on fingerprints alone loses records, and width is chosen against the lifetime distinct-key count rather than today's volume.

for a principal

Frame it as a design constraint rather than a defect. Because no function removes the case, the system owes an explicit answer for what happens on a collision, and the width choice is a cost trade against that answer.

## The setting A **fingerprint** here is a fixed-width value computed from a key and stored in place of it: a routing tag, a dedupe marker, a cheap pre-filter in front of an expensive comparison. Suppose the field is 32 bits wide and a stream of distinct keys is fingerprinted into it. The interesting question is not whether the function is any good. It is how many distinct values the field can hold at all. Thirty-two bits hold exactly `2^32 = 4,294,967,296` distinct patterns. The keys form one set, the fingerprints another, and the function is a map from the first into the second. The **pigeonhole principle** states that if a finite set of size N is mapped into a set of size M with N greater than M, then some target value receives at least two sources; equivalently, **no injective (one-to-one) map exists from a larger finite set into a smaller one**. Take N to be the number of distinct keys and M to be 2^32, and the conclusion is immediate: past 4,294,967,296 distinct keys, two of them share a fingerprint. This is arithmetic about two counts, not a claim about any particular function. ## Why the function is irrelevant to the threshold Engineers reach instinctively for the function when they hear *collision*, and that instinct is misplaced for this claim. What the function controls is how much **sooner than the threshold** collisions appear in practice, and how hard it is for someone to manufacture one on purpose. What it cannot control is the size of the output space. A perfectly uniform function and a badly skewed one have the same forced threshold, because the threshold is set by the width alone. The negative direction of the map is the one that stays sound: because the function is deterministic, **equal keys always produce equal fingerprints**, so *different fingerprints prove different keys*. Only the positive direction — equal fingerprints — carries no conclusion. ## Width and threshold | Fingerprint width | Distinct values | Distinct keys that force a repeat | |---|---|---| | 16 bits | 65,536 | 65,537 | | 24 bits | 16,777,216 | 16,777,217 | | 32 bits | 4,294,967,296 | 4,294,967,297 | | 64 bits | about 1.8 x 10^19 | one more than that | Each bit dropped halves the space, so truncation is not a small economy. Taking the leading 32 bits of a 256-bit digest is a reduction of the value space by a factor of 2^224, and the point at which a repeat is forced falls by the same factor. ## What follows for the code around it - **A fingerprint match gates a comparison, it does not replace one.** Where equality matters, the match selects candidates and a full comparison of the keys decides. - **Deduplication keyed on fingerprints alone silently loses data.** Two distinct records that collide are treated as one, and the loss leaves no error behind to notice. - **We have never seen a collision** is a statement about how many distinct keys have been through the system so far, not evidence that the field is wide enough for the next order of magnitude. - **Size the width against the lifetime distinct-key count, with margin.** The forced threshold is where collisions become certain, not where they start. - **Decide the collision behaviour up front** — chain, compare, reject, or widen — because the case cannot be designed away. ## What the argument does not claim Three limits are worth stating explicitly, because each is a common over-reading: 1. **It says nothing below the threshold.** Fewer distinct keys than values means no repeat is *forced*; it does not mean none occurs. How likely one is at that point is a question about probability, not about counting, and it is answered by a different kind of argument. 2. **It names no pair.** The argument proves that two colliding keys exist and gives no way to point at them; finding them is a separate search over the data. 3. **It is not a verdict on the function.** A collision above the threshold is expected behaviour of every function of that width, so treating one as a defect report misreads the situation. ## The same count elsewhere The identical argument settles a family of fixed-width design questions: short public identifiers drawn from a limited alphabet and length, bucket indexes over a fixed table size, and sequence numbers that wrap within a fixed field. In every case the design question is the same one — how many distinct things will pass through, against how many distinct values the field can represent — and the answer never depends on how cleverly the value is derived.

  • Does using a cryptographic digest instead of a fast one change when collisions become unavoidable?
    No. The threshold is fixed by output width alone: a k-bit output has 2^k values, so 2^k + 1 distinct keys force a repeat whichever function produced them. Cryptographic strength makes collisions hard to find deliberately and keeps the spread even; it adds no values to the output space.
  • If two records share a fingerprint, what may you conclude about the records themselves?
    Only that the map sent them to the same value. They may be equal or entirely unrelated, so a match is evidence to check rather than a decision. The opposite direction is sound: different fingerprints prove the records differ, because a deterministic function sends equal inputs to equal outputs.
  • What exactly is given up by truncating a wide digest to its leading bits?
    Value count, by a factor of two per bit discarded. Cutting a 256-bit digest to 32 bits shrinks the space from 2^256 to 2^32 and moves the forced threshold down by the same factor. Collisions stop being a theoretical property and become something ordinary traffic will reach.

saying these in an interview costs you the question

  • Claims a good enough function makes collisions impossible
  • Treats equal fingerprints as proof that two records are equal
  • Believes collisions only begin once every value has been used
  • Says widening to 64 bits removes collisions rather than deferring them
  • Confuses the forced threshold with the point where a collision is merely likely
  • Reports a collision above the threshold as a defect in the function
open as a page

To attack by contradiction the claim that an allocator never hands one block to two callers, what do you assume?

level: middleimportance: must knowfreq 62%

basics

~20 s

Assume the claim fails: that some run exists in which one block is held by two callers at once. That single assumed run is a concrete object you can trace until it forces something impossible.

open as a page

Why prove the contrapositive of 'if a block is on the free list, no live pointer refers to it'?

level: middleimportance: must knowfreq 58%

basics

~20 s

The contrapositive — if a live pointer refers to a block, that block is not on the free list — states the same claim, but starts from a fact you can trace: a pointer, and the call that produced it.

open as a page

When a reviewer calls an induction proof circular, what does the inductive hypothesis actually let you assume?

level: middleimportance: must knowfreq 62%

basics

~20 s

Only smaller cases, never the case being proved. The step establishes an implication - if the claim holds at k, it holds at k+1 - and the base case supplies the first true instance, so nothing is assumed about the target.

open as a page

Why does proving that every integer above 1 factors into primes require strong induction?

level: middleimportance: must knowfreq 52%

basics

~20 s

Because a composite splits as n = a times b, where a and b can be far smaller than n - 1. A previous-value hypothesis says nothing about them; the strong form assumes the claim for every value below n, which does cover both factors.

open as a page

A loop drains a paged result set into a sink; what three obligations must its loop invariant meet to prove the result?

level: middleimportance: must knowfreq 62%

basics

~10 s

A loop invariant must be true before the first pass (initialization), be re-established by every pass (maintenance), and, together with the failed loop guard, imply the result the loop promised (exit).

open as a page

In a binary search over a sorted page index, which invariant about the current bounds makes the exit answer correct?

level: seniorimportance: must knowfreq 54%

basics

~20 s

The window invariant: if the target key occurs in the index at all, its position lies inside the current bounds. Each comparison discards only the side the sort order proves cannot hold it, so an empty window at exit proves absence.

open as a page

What does a single counterexample settle about the written claim that every schedule this scheduler produces is fair?

level: middleimportance: should knowfreq 48%

basics

~10 s

One exhibited run in which a ready task is passed over forever settles the claim as written: it is false, and no more evidence is needed. It says nothing about how often that happens.

open as a page

What does proving partial correctness of a page-draining loop leave unproven, and what closes that gap?

level: middleimportance: should knowfreq 46%

basics

~20 s

Partial correctness claims only that if the loop halts, its result meets the specification. It leaves halting itself unproven. A termination argument — a quantity that strictly decreases and cannot fall forever — closes the gap and gives total correctness.

open as a page

Ten thousand session keys hash across 96 shards; what can you assert about the busiest shard without measuring?

level: middleimportance: should knowfreq 50%

basics

~20 s

Some shard holds at least ceil(10000/96) = 105 keys. The generalized pigeonhole principle says that with n items in m boxes, one box always holds at least ceil(n/m), whatever the distribution turns out to be.

open as a page

In review, how do you spot a correctness argument that quietly assumes the invariant it claims to establish?

level: seniorimportance: should knowfreq 40%

basics

~20 s

List what each step rests on and name the source of every one. A circular argument has a step whose only support is the claim itself, wearing another name: a documented precondition, a helper's contract, or a passing test.

open as a page

A recursive routine works on large inputs but fails on two-block inputs, yet its induction proof checked a one-block base case - where is the gap?

level: seniorimportance: should knowfreq 38%

basics

~20 s

Almost certainly in the step's own preconditions: it needs more than one block before its argument applies, so it never links the one-block base to the next size up. Because every later case rests on the missing ones, nothing above the base is proved.

open as a page

For a hash tree defined recursively over data blocks, how do you prove a property of every such tree without an integer n?

level: seniorimportance: should knowfreq 44%

basics

~20 s

Induct over the data definition instead of a number: prove the property for each base constructor, such as a leaf holding one block, then for each combining constructor while assuming it of the immediate subtrees. Every finitely built tree is then covered.

open as a page

Which property must a loop's termination measure have for the loop to be guaranteed to stop, not merely usually stop?

level: seniorimportance: should knowfreq 42%

basics

~20 s

The measure must strictly decrease on every pass without exception and be bounded below, so it cannot fall forever. A measure that decreases on most passes proves nothing: the exceptional passes are exactly where a loop hangs.

open as a page

Every module in a build declares at least one prerequisite module; why must a dependency cycle exist?

level: seniorimportance: should knowfreq 38%

basics

~20 s

Because the module set is finite. Take a longest chain of distinct modules, each waiting on the next; the last module's prerequisite cannot be new without extending the chain, so it lies inside it and closes a cycle.

open as a page

When should a drain loop's termination be guaranteed structurally by a hard bound rather than trusted to the data source?

level: principalimportance: should knowfreq 33%

basics

~20 s

Whenever the termination measure depends on a party you do not control, termination is an assumption about the environment rather than a property of your code. Bound it structurally, and make the bound report a failure instead of silently truncating the result.

open as a page

Why can a loop invariant that genuinely holds on every pass still fail to prove the loop's result at exit?

level: seniorimportance: nice to knowfreq 28%

basics

~20 s

Because holding is only two of the three obligations. The exit step needs the invariant plus the failed guard to imply the postcondition, and a claim can be perfectly true every pass while carrying none of the information the postcondition is about.

open as a page

A counting argument proves two records in a batch share a fingerprint; what does that argument not tell you?

level: seniorimportance: nice to knowfreq 27%

basics

~20 s

It names no pair, no position and no cost of finding one, and it says nothing about smaller batches or about how often collisions occur. It establishes existence only; a scan still has to produce the witness.

open as a page

When a written invariant resists a direct argument, how do you decide between pressing for a contradiction proof and hunting a counterexample?

level: principalimportance: nice to knowfreq 30%

basics

~10 s

Run both and let each steer the other: where the argument stalls names the state worth searching, and a near-miss witness names the missing hypothesis. Spend according to what else rests on the claim.

open as a page

A proposal adds a third node kind to a recursively defined hash tree - how should the induction burden it creates weigh in that decision?

level: principalimportance: nice to knowfreq 22%

basics

~20 s

Every constructor in a definition is a case in every structural-induction argument over it, for all the properties and all the years that definition lives. Price the new kind as one extra case per property, and prefer a derived form that expands into the existing constructors.

open as a page