Skip to content
Preptima
mediumConceptEntryMidSenior

Your team reports 90 per cent line coverage. What does that number tell you, and what does it not?

Line coverage tells you which lines executed while the tests ran, which is not the same as which behaviours were verified: a test with no assertions still counts, and the interesting information is in the uncovered lines and in whether the tests would fail if the code were wrong.

4 min readUpdated 2026-07-29Target archetype: Big Tech, Enterprise Captive, Product Startup
Practice answering out loud

What the interviewer is scoring

  • Does the candidate separate lines executed from behaviour verified
  • Whether line, branch and condition coverage are distinguished rather than conflated
  • That a test with no assertions is recognised as still producing coverage
  • Whether the uncovered portion is treated as the more informative half of the report
  • Does the candidate defend coverage as a diagnostic while rejecting it as a target

Answer

What the number actually measures

A coverage tool instruments the code, runs the tests, and records which lines were executed at least once. Ninety per cent line coverage therefore means that ninety per cent of executable lines ran during the test suite. That is the whole claim, and it is a claim about execution rather than about verification.

The gap between those two ideas is where every misuse of the metric comes from. Executing a line proves the test reached it, and says nothing about whether the test checked the result, checked the right thing, or would have noticed had the line been wrong. Coverage is a necessary condition for catching a defect — code that never runs cannot be tested — and nowhere near a sufficient one.

Line, branch and condition are three different numbers

Line coverage is the weakest of the common variants, and quoting it without saying which one you mean is itself a small signal.

Consider if (a && b) doThing(); on one line. A single test with both a and b true executes the line and reports full line coverage, having never observed what happens when the condition is false. Branch coverage insists that each branch of each decision is taken at least once, so it demands the false case too, and it is the number worth reporting for most codebases. Condition coverage goes further and requires each sub-expression to be evaluated both ways, which catches the case where b was never false and the second half of the conjunction is untested. Standards for safety-critical software require a stricter form still, in which each condition must be shown to independently affect the outcome, precisely because branch coverage lets compound conditions hide.

The practical consequence is that the branch figure for the same suite is often much lower, and that lower number is the more honest description.

A test with no assertions covers plenty

The cleanest demonstration that coverage measures the wrong thing is that you can raise it without testing anything. A test that calls a function inside a try block, catches everything, and asserts nothing will execute every line it touches and contribute to the number. So will a test whose assertions are all on inputs rather than outputs, or one whose single assertion checks that the call did not throw. None of these would fail if the function's logic were inverted.

This is what happens whenever a team is asked to raise coverage under time pressure, because writing an exercising test is fast and writing a verifying test requires understanding what the code should do. A suite built that way is worse than a smaller one: it costs run time, has to be maintained, and creates confidence in proportion to its uselessness.

The uncovered tenth is where the information is

The aggregate number is nearly useless for making decisions; the report underneath it is genuinely valuable. Read the uncovered lines and ask why each is uncovered, because the answers separate cleanly into groups that need different responses.

Some are error-handling paths, which is the most common and most consequential category. Those paths run in production during incidents and have therefore never been executed at all, which is why a catch block containing its own bug is such a familiar pattern. Some are genuinely dead code, and the correct action is deletion rather than a test. Some are trivial getters and generated code where coverage is noise. And some are core business logic, which is the finding that should stop the conversation: a payment calculation at zero coverage matters more than the aggregate being ninety rather than eighty-five.

Weighted by risk, in other words. Ninety per cent overall with the pricing engine at forty is worse than seventy per cent overall with the pricing engine at ninety-five, and no single number can express that.

What happens once the number becomes the target

Set a coverage threshold and the metric stops measuring what it measured, because people optimise what is measured. The predictable outcomes are assertion-free tests written to reach a percentage, tests that exercise generated or trivial code because it is cheap, exclusion annotations applied to whatever is hard to cover, and — the worst one — engineers arguing to keep a useless test because deleting it drops the number below the gate.

A threshold on the direction of travel is weaker than it appears too, since blocking a merge because coverage fell penalises the change that deletes a large well-tested module. The defensible version is narrow: require that new or modified lines are covered, which is a question about this change rather than the codebase's history, and pair it with review of whether those tests assert anything.

The metric that answers the real question

If what you want to know is whether the tests would catch a bug, measure that directly. Mutation testing makes small changes to the code — inverting a condition, altering a boundary, removing a call — and reports whether any test fails as a result. A mutant that survives is a change to your production code that your suite does not notice, which is exactly the question coverage cannot answer, and a file at 100 per cent line coverage with a low mutation score is a file full of tests that execute code without checking it.

It costs far more to run, so it belongs where correctness is expensive rather than across everything. Run it once on your most critical module and the result usually settles the argument about whether the coverage number was telling you anything.

Likely follow-ups

  • What would you measure instead if you wanted to know whether the tests would catch a bug?
  • A file sits at 100 per cent and still ships a defect. Give me a way that happens.
  • Would you block a merge when coverage falls? What goes wrong with that rule?
  • Which parts of a codebase do you deliberately not care about the coverage of?

Related questions

code-coveragebranch-coveragemutation-testingassertionstest-quality