skip to content

In a static type checker, what is the difference between soundness and completeness?

level: seniorimportance: nice to knowfreq 21%

answer

  1. Two directions: what passes, what is blocked
  2. Map each to a false-negative or false-positive
  3. Both at once is undecidable
  4. Escape hatches void the guarantee quietly
  5. Enumerate the holes, then audit them

basics

~20 s

A sound checker rejects every program that would commit a type error at run time, so it has no false negatives. A complete checker accepts every program that would not, so it has no false positives. Real checkers give up some of one.

solid answer

~50 s

Soundness is about what gets through: a sound checker never certifies a program that will hit a type error at run time. Completeness is about what gets blocked: a complete checker never rejects a program that would have been fine. Both at once is impossible for a terminating checker on a realistic language, because the underlying semantic questions are undecidable, so every checker approximates in one direction. Purely sound checkers reject safe programs, and teams answer with escape hatches — which quietly destroys the guarantee anyway. Most industrial checkers are therefore deliberately unsound at a small, documented set of points: unchecked type assertions, dynamic member access, mutable containers treated covariantly, interop with unannotated code, and any data that only exists at run time. That is a defensible engineering position, provided the team can enumerate the holes and audit them.

go deeper

for a junior

Learn the two directions first: soundness is about unsafe programs slipping through, completeness is about safe programs being blocked. Being able to state that much correctly already puts you ahead.

for a middle

Be ready to map each term to false negatives and false positives and to explain why a terminating checker cannot have both. Name at least one place your checker is deliberately permissive.

for a senior

Demonstrate that you manage the holes: which escape hatches exist, where they cluster, and how an incident traced back to one. Interviewers at this level want an inventory, not a definition.

for a principal

Own the position that unsoundness is a tradeoff to be governed, not eliminated: decide where escape hatches are banned outright, what evidence replaces them, and how the team measures drift.

## The two words, precisely Think of the checker as a classifier over programs, with the ground truth being "would this program actually commit a type error on some execution?" - **Sound**: if the checker accepts a program, the program does not commit a type error. Equivalently: every unsafe program is rejected. A sound checker has **no false negatives** — nothing unsafe slips through. - **Complete**: if a program does not commit a type error, the checker accepts it. Equivalently: no safe program is rejected. A complete checker has **no false positives** — nothing safe is blocked. The classic slogan for soundness is that well-typed programs do not go wrong. The slogan carries a hidden clause: *go wrong in the ways this particular type system models*. A sound system for absence still permits a wrong amount to be paid; soundness is relative to the property the types express, never to correctness in general. ## Why you cannot have both Whether an arbitrary program ever performs a given operation on a given value is undecidable in general — the same wall that makes non-trivial semantic properties undecidable. A checker must terminate and must answer, so it approximates: it either errs toward rejecting some safe programs (sound, incomplete) or toward accepting some unsafe ones (unsound, more permissive). This is not a defect of any particular tool. It is the shape of the problem, and it is the same tradeoff every other static analysis makes; type checking is just the case where the industry has settled on a very specific, very fast approximation. ## Where real checkers choose unsoundness on purpose Mainstream checkers lean toward usability. The recurring deliberate holes are worth naming, because knowing the list is what separates a senior answer from a textbook one: - **Unchecked assertions.** A construct that tells the checker "treat this value as this type" with no verification. It is the escape hatch by which every other guarantee can be voided in one line. - **Data from outside the program.** Parsed payloads, configuration, stored records. The declaration is an assumption; nothing verifies it unless code does. - **Interop with unannotated code.** Values crossing in from a region the checker does not analyse. - **Mutable containers used covariantly.** Many systems accept treating a container of a subtype as a container of a supertype, which is fine for reading and unsafe for writing. - **Reflective or dynamic member access.** Constructing a member name at run time defeats a static model of members. - **Out-of-band mutation.** A field narrowed by a branch condition can be changed by something else before the narrowed use — a classic gap between what the checker proved and what happens. ## Why full soundness is not automatically better A strictly sound checker rejects safe programs, and every rejection lands on a developer under deadline. The observed response is not to restructure the code; it is to reach for the escape hatch. A codebase that is sound-in-theory and riddled with assertions has a *worse* safety profile than an honestly unsound one, because the holes are unlabelled and the team believes the guarantee holds. So the engineering question is not "is this checker sound?" but "is its unsoundness enumerable, greppable, and audited?" ## An audit worth copying A 4-person team owning a payroll engine investigated a partial-failure rollback that credited 47 employees twice. The root cause was one unchecked assertion added months earlier to silence three errors when a record shape changed; the value asserted to be a settled posting was in fact a draft, and the compensating routine happily reversed it. The response was not to demand a sounder checker. It was to make the unsoundness visible: an audit found 74 unchecked assertions across 1,163 files, 9 of them on money-handling paths; 2 of those 9 were wrong. The team then required every assertion to carry a one-line justification and forbade them entirely inside the money core, where validation functions replaced them. The point of the story is the reframing — the checker's holes are a managed inventory, not a mystery. ## Answering it well Give both definitions in the accept/reject direction, map each to false negatives and false positives, say plainly that both at once is impossible, and then get concrete about which holes your checker has and how the team keeps them countable. Avoid claiming that any mainstream checker is fully sound, and avoid dismissing unsoundness as sloppiness — it is a deliberate, defensible tradeoff that turns bad only when it is undocumented.

  • Why might a deliberately unsound checker be the better engineering choice?
    Because the alternative's cost is paid in rejected safe programs, and developers answer those rejections with escape hatches rather than redesign. A sound-in-theory codebase full of unchecked assertions is less safe than an honestly unsound one, because the holes are invisible and the team trusts a guarantee that no longer holds. The usable checker with a short, documented, greppable list of unsound points is the defensible position.
  • How would you audit a codebase for the checker's escape hatches?
    Make them countable first: search for unchecked assertions, suppression comments, dynamic member access, and functions that accept values from unannotated regions. Then rank by blast radius rather than by count — the ones on money, authorisation, or rollback paths matter, the ones in a formatter do not. Require a justification comment on each, forbid them outright in the highest-risk modules, and track the density over time as a trend, not a gate.
  • Does soundness mean a well-typed program cannot fail?
    No. Soundness is always relative to the property the type system models. A system sound with respect to absence guarantees you will not use a missing value as if it were present; it says nothing about a wrong amount, a wrong order of operations, an exhausted resource, or a race. Candidates who state the slogan without that qualification usually also believe a green compile implies correctness.

saying these in an interview costs you the question

  • Uses sound and complete as interchangeable words
  • Claims a mainstream checker is fully sound
  • Thinks soundness means the program cannot fail
  • Treats deliberate unsoundness as pure sloppiness
  • Cannot name a single escape hatch in their checker
  • Believes stricter settings alone restore soundness

context