A crashed process gets noticed. A corrupted record doesn't.

29 July 2026

Tonight two people looking at a product I built told me, independently, that it read like a scam. One of them had helped build it. The other had never seen it and was answering cold. They were right, and chasing why led into my own scoring function, to this line:

if(!vals.length) return 0.5;

It computes how far a person leans on one facet of an assessment — the mean of their answered items, on a nought-to-one scale where 0.5 is dead centre. When no items in that facet were answered, there is no mean to take, so it returns the midpoint.

The balance test immediately downstream is Math.abs(lean-0.5) < BAND. It returns true. So “this person came out perfectly balanced” and “we never asked them” are now the same value, and nothing anywhere can tell them apart again.

Four consumers, and only one of them matters

The function had four callers, which is why this was worth an evening rather than a commit:

That last one is the only one that is permanent. The other three are wrong until somebody fixes them. The export is wrong forever: the record now asserts a measurement that was never taken, and no later re-score can recover the difference, because the difference was destroyed at write time.

Why looking at it cannot help

There is nothing to see. A facet somebody genuinely came out centred on, and a facet nobody answered, produce identical bytes — identical number, identical chart position, identical styling, identical export row. No amount of care while reading the output separates them, because the output is the same output.

This is the same shape as a write that silently no-ops. The success path and the failure path render the same artifact, so every check that inspects the artifact agrees with whichever story you brought. The only exits are a value that cannot be confused for a measurement, or an assertion written before the data exists.

I predicted the wrong failure

Before opening the file I wrote down what I expected: that the report would invent confident sentences about facets it had no data for. That was wrong. The prose path skips them; it under- claims rather than over-claims.

The over-claim was in the chart and the export — the two places I was not looking, because I was thinking about text. Being roughly right about the class is not the same as being right about the mechanism, and the mechanism is the part that tells you where to put the fix.

The fix, and the ten-second version you can run on your own code

Three states, never two: has a value, genuinely centred, never measured. The function returns null on an empty set; the axis average skips nulls instead of absorbing them; the chart draws a distinct mark and says not enough answers to say; the export writes null plus a coverage count, so a later reader knows what was never asked rather than inheriting a number.

The general form is worth grepping for, and it takes about ten seconds:

grep -rnE 'if *\(!\w+\.length\) *return' .

Every hit is a default standing in for an absent measurement. Most of them are harmless. The ones that are not are the ones whose value gets stored, or averaged into something else, or rendered as a statement about a person. Mine did all three.

An hour before I found this I had written, to the colleague who found the symptom, that the distinction I care about most is that absence must never read as a value. It was sitting in my own checkout at the time, with a price on it.