What is the difference between line coverage and branch coverage in a test run?
answer
- Two metrics, two different denominators
- One line can hide a whole decision
- Outcomes taken, not lines touched
- Fourteen guards, one test, half the outcomes
- Decision coverage subsumes statement coverage
basics
~20 sLine coverage counts source lines executed at least once. Branch coverage counts which outcomes of each decision were taken, the true side and the false side. A line can execute while one of its two outcomes never runs.
solid answer
~40 sLine coverage asks whether a source line executed at least once; branch or decision coverage asks, for every decision point, whether every outcome of it was taken. Because a whole decision can sit inside a single line, a suite can execute every line while exercising only one side of each condition. The standard demonstration is a scoring function built from fourteen single-line guards, driven by one test that satisfies all fourteen: 100% line coverage, 50% branch coverage. In structured code full decision coverage subsumes statement coverage, so branch is the stricter of the two and the one worth reading first. Method or function coverage is coarser still, recording only that a function was entered. All three are structural: they describe what ran, not what was checked.
code
pseudocode · 10 linesfunction completenessScore(form):
score = 0
if form.weightKg != null: score = score + 1
if form.bpSystolic != null: score = score + 1
if form.adverseEventNote != "": score = score + 1
return score
test_fullyFilledForm():
form = visitForm(weightKg = 71.4, bpSystolic = 118, adverseEventNote = "none")
assert completenessScore(form) == 3go deeper
Be ready to define each metric in one sentence and give the standard example where every line runs but only one side of each condition is taken. Knowing which denominator each percentage uses is most of the answer.
Explain how a decision hides inside a single line — single-line guards, short-circuiting operators, conditional expressions, unwritten else arms — and why full decision coverage subsumes statement coverage in structured code while the reverse fails.
Expect to read a real report aloud: which lines are only partially covered, where the branch gap concentrates, and whether a loop or exception edge explains it. Say plainly what neither metric can see, such as iteration counts.
Own which structural metric a team reads by default and what its denominator does to comparisons. Branch percentages across a straight-line mapping module and a conditional-dense validation module measure different things and should not be ranked side by side.
## What structural coverage measures Structural coverage answers one narrow question: which parts of the production code did this test run execute? Every metric in the family shares that definition and differs only in what it counts as "a part" — a line, a statement, a decision outcome, a function. Understanding the differences is really understanding the denominators. ### Line and statement coverage A statement is one executable instruction; a line is a unit of source text. They are not the same unit: one line can hold several statements, and one statement can span several lines. Statement coverage is executed statements over executable statements. Line coverage is lines containing at least one executed statement over lines containing at least one executable statement. Blank lines, comments and declarations that compile to nothing are excluded from the denominator, which is why the percentage you see is never "percentage of the file". Line coverage is popular because it maps straight onto the editor gutter. It is also the weakest of the useful signals, because a line is a unit of text, not a unit of control flow. ### Branch, or decision, coverage A decision is any point where control can continue in more than one direction: a conditional, the continuation test of a loop, a multi-way selection, an exception edge, a conditional expression, or a short-circuiting boolean operator. Every decision has at least two outcomes. Branch coverage counts outcomes taken over outcomes available, so its denominator tracks roughly twice the number of decisions rather than the number of lines. Two consequences follow. First, a line can be executed while only one of the outcomes on it has been taken; many reports show this as a distinct third state, "partially covered", beside covered and uncovered. Second, in structured code full decision coverage subsumes statement coverage: if every outcome of every decision has been taken, every reachable statement has been reached. The reverse does not hold, and that asymmetry is the point of the question. ### The canonical demonstration A completeness scorer for a clinical-trial visit form has fourteen optional fields, each guarded by a single-line conditional that increments a counter. One test submits a completely filled form. Every line executes, so line coverage reads 100%. Only the true outcome of each guard was taken, so branch coverage reads 14 of 28 outcomes, 50%. The untouched half is exactly the interesting half — what the scorer does when a field is missing. A report showing 91.7% line and 58.2% branch on that module is telling the same story in a less dramatic form, and the branch column is where the untested behaviour hides. ### Where the gap comes from in practice - **Implicit false arms.** A conditional written without an else still has a false outcome in the control-flow graph. - **Short-circuiting operators.** A two-term boolean expression is two decisions inside one line; a suite that never makes the first term false never takes one of them. - **Conditional expressions** that compile to a branch while occupying a single line. - **Default arms** of a multi-way selection that the source never wrote out. - **Exception edges**, where "the guarded region threw" is an outcome nobody exercises. - **Loop bodies executed zero times** — the exit-immediately outcome of the loop's continuation decision. ### What branch coverage still does not distinguish Branch coverage records that a loop's continuation decision went both ways. It does not record how many iterations ran: a loop that iterates fifteen times and one that iterates once are indistinguishable to it. Nor does it record which combination of conditions produced an outcome; that is condition-level and path-level material, and it is why stricter criteria exist above decision coverage. ### Function and method coverage The coarsest member of the family: a function counts as covered if it was entered at least once. It says nothing about what happened inside — a function entered and returned on its first guard is fully covered by this metric and barely covered by the other two. Its value is triage. On a large or inherited module it shows instantly which whole units no test has ever touched, and it is cheap enough to glance at on every build. Some reports publish a file- or type-level equivalent for the same reason. ### Reading a report without being misled Read the fraction, not only the percentage: 47 of 51 lines and 470 of 510 lines are the same percentage and very different amounts of untested code. Compare like with like — a straight-line data-mapping module and a validation module dense with conditionals have structurally different denominators, so their branch percentages are not directly comparable. And keep in mind what the whole family shares: these metrics observe execution. They record what ran. Whether the run checked anything at all is a different question, answered with different instruments.
- What does method or function coverage add that line coverage does not?It is coarser, not finer: a function counts as covered the moment it is entered, however little of its body ran. Its value is triage on a large module — it names the units no test has ever reached, which line coverage buries inside a single averaged percentage. Never present it as a stricter signal than line or branch coverage; a function that returns on its first guard is 100% covered by it.
- A loop body runs fifteen times in one test and once in another. Does branch coverage differ between the two runs?No. The loop contributes one decision with two outcomes — continue into the body, and exit — and both runs take both. Iteration counts are not part of the metric, which is why boundary bugs at the first or last iteration survive a fully green branch report. If iteration behaviour matters, that has to come from deliberate cases at zero, one and many, not from the coverage percentage.
- Why do some reports mark a line as only partially covered?Because more than one branch outcome lives on that line and the run took some of them. A single-line conditional, a conditional expression, or a short-circuiting boolean operator all put two or more outcomes on one line. The line executed, so it cannot be shown as uncovered; not every outcome was taken, so it cannot be shown as covered. The third state exists to make that visible in the gutter.
Line coverage is a register of which rooms in a building someone walked into; branch coverage is a register of which doors were opened in each direction. You can visit every room without ever having tried the back door.
saying these in an interview costs you the question
- Says line and branch coverage are the same measure renamed
- Thinks executing a line means every outcome on it was taken
- Believes 100% statement coverage implies 100% branch coverage
- Counts loop iterations as separate branch outcomes
- Treats function coverage as stricter than line coverage
- Claims an unwritten else arm has no branch outcome to cover