A dead-code gate cannot be exact, so which way should it err, and what does that choice cost you?
answer
- pick which error you can live with
- one-sided error, never no error
- unknown has to count as reachable
- superset of behaviour, subset of dead code
- polarity of the claim flips false positive
basics
~20 sErr toward calling code reachable. Over-approximating reachability means everything the gate reports as dead really is dead, so deletion stays safe. The cost is real dead code that goes unreported, and behavioural warnings built on the same model that fire on impossible paths.
solid answer
~50 sSince exactness is unavailable, the only decision left is which one-sided error you accept. A gate that **over-approximates** the program's reachable behaviour computes a superset of what can actually happen: nothing real escapes it, so when it says a block is unreachable the block truly is unreachable and deleting it is safe. What it gives up is coverage — genuinely dead code it cannot prove dead stays reported as live — and, on any warning built from the same model, findings about paths that cannot occur. A gate that **under-approximates**, by exploring a bounded set of real executions, has the opposite profile: everything it reports is real, and everything it misses is silent. For a claim that authorises deletion you want the first direction, because the deletion must never be wrong. The key move is noticing that 'false positive' flips meaning with the polarity of the claim.
code
pseudocode · 12 linesreachable = { entry }
worklist = [ entry ]
while worklist is not empty:
b = pop(worklist)
for each successor s of b:
cond = branch condition leading from b to s
if proved_never_true(cond):
continue # proven impossible: leave s unmarked
if s not in reachable: # unknown counts as reachable
add s to reachable
push s onto worklist
dead = all_blocks minus reachable # only provably unreachable blocksgo deeper
A warning you cannot reproduce is not automatically a tool bug. The check is answering a question it can only approximate, so 'could not rule this out' is a legitimate thing for it to say.
Explain the two one-sided errors — reporting something that cannot happen, versus staying silent about something that can — and work out which one a given check has chosen.
Show the operating decision: which checks block a merge, which only advise, how you measure the survival rate of findings, and how you stop the suppression list from becoming the real policy.
Own the asymmetry in cost. A missed defect and an ignored warning both erode the gate, and the right balance differs between a payment path and a nightly report, so the direction is set per check rather than globally.
## The question underneath the gate 'Is this block dead?' means 'is there no input on which control reaches it?'. That is a question about the program's behaviour, and no procedure answers it correctly for every program. The gate's author therefore did not implement that question. They implemented a decidable neighbour of it and chose, deliberately or by accident, which way the neighbour is allowed to be wrong. ## Two ways to be wrong, and only two An analysis works over a model of what the program can do. There are exactly two useful relationships between the model and reality. 1. **Over-approximation.** The model contains every behaviour the program actually has, plus some it does not. Consequence: if the bad thing is absent from the model, it is absent from the program — so *proofs of absence* are trustworthy. But the model's extra behaviours can trigger reports about situations that cannot occur. 2. **Under-approximation.** The model contains only behaviours the program really has, but not all of them — a bounded search, or a set of executions actually observed. Consequence: everything reported is *exhibited*, so it is real. But absence of a report proves nothing. There is no third option that is neither, and no budget converts one into the other. This is the shape the undecidability forces: the tool must pick an error direction, and the only engineering freedom left is precision inside that direction. ## Which direction each claim needs | Claim the tool makes | Model relationship | Wrong answers it can give | Wrong answers it cannot give | |---|---|---|---| | This block is unreachable, delete it | over-approximates behaviour | leaves genuinely dead code in place | marks a live block dead | | This call site can receive an absent value | over-approximates behaviour | warns about a path that never runs | stays silent about a real one | | This bounded search found a failing input | under-approximates behaviour | misses a failure beyond the bound | reports a failure that cannot happen | The first two rows come from the **same** over-approximation, which is the point most candidates miss. A superset of the reachable states makes the complement — the set the tool is willing to call dead — a *subset* of what is truly dead. So the same conservatism that produces noisy warnings produces quiet, trustworthy deletions. 'False positive' is not a property of the analysis; it is a property of the analysis combined with the polarity of the sentence it prints. ## What this means for the block being argued about When an author insists the flagged code is fine, first identify which claim the check makes. - If the check is a **warning** — 'this may happen' — the author is probably right about their specific path and the tool is still behaving correctly. The question is whether the check's survival rate justifies keeping it on. - If the check is a **deletion recommendation** — 'this cannot happen, remove it' — and the author can demonstrate the code running, that is a genuine defect in the tool. A sound over-approximating gate is not allowed to make that mistake, and an unsound one must not be wired to anything automatic. That distinction should also decide the gate's authority. Checks that only speak when they have proof can block a merge, because a block costs nothing false. Checks that report what they cannot rule out should advise, because blocking on a hypothesis is how a team learns to suppress everything. ## Precision is the real lever Both directions leave one honest way to improve: make the model closer to reality without changing which side it sits on. Tracking which branch conditions can hold together, distinguishing call sites, or modelling a value's origin all shrink the set of impossible behaviours in the model. That reduces noise while keeping the guarantee intact. Turning down the report volume by dropping low-confidence findings does not do this — it quietly converts a sound check into an unsound one and sacrifices the guarantee you were paying for. ## The trap The common failure is treating a missed defect and a spurious warning as the same kind of error and averaging them into one accuracy number. They are not commensurable: one is discovered in production and the other in review. Decide the direction per check, from the cost of each failure on the path being analysed, and say so in the check's own documentation. A gate whose direction nobody can state is a gate nobody can reason about, and it ends up governed by its suppression list rather than by its rules.
- Your under-approximating checks find real defects but prove nothing. When is that the better trade?When an ignored warning costs more than a missed defect. On a large existing codebase a sound analysis can report thousands of findings, the team disables it, and the guarantee is worth nothing. A search that only reports what it can exhibit keeps credibility, and you pair it with a sound gate on the few paths where absence really must be proved.
- Why does 'this code is dead' need the opposite error direction from 'this value may be absent here'?Because the claims have opposite polarity. A deletion claim must never be wrong, so the tool may only report what it has proved, and therefore under-reports. A warning is a hypothesis, so the tool may report anything it cannot rule out, and therefore over-reports. One and the same over-approximation of behaviour produces both profiles.
- How do you reduce spurious warnings without weakening the guarantee?Improve the model's precision rather than filtering its output. Tracking which conditions can hold simultaneously, separating call sites, or following where a value came from all remove behaviours that the program cannot have. Dropping low-confidence findings instead changes the error direction and silently gives up the absence proof you were buying.
A smoke alarm that never misses a fire will also go off at burnt toast, and one that never cries wolf will eventually sleep through something. You choose which error you can staff for, not whether to have one.
saying these in an interview costs you the question
- Wants a gate with neither false positives nor missed defects
- Deletes any block the analyzer could not prove reachable
- Calls a tool unsound merely because it warns too often
- Treats a missed defect and a spurious warning as equivalent
- Assumes a larger analysis budget removes both error kinds
- Filters low-confidence findings and still claims a proof of absence