2026-06-12
This sensor pipeline deduplicates measurement readings while preserving order. The unit tests pass, code review approves it, and it ships. A week later, downstream consumers complain that error sentinels are appearing multiple times in the "deduplicated" output.
import json
def deduplicate(values):
"""Remove duplicate measurements, preserving first-seen order."""
seen = set()
unique = []
for v in values:
if v not in seen:
seen.add(v)
unique.append(v)
return unique
# --- unit test (passes) ---
NAN = float('nan')
result = deduplicate([1.0, NAN, 2.0, NAN, 3.0])
assert result == [1.0, NAN, 2.0, 3.0] # ✓ passes
# --- production code path ---
payload = '{"readings": [1.0, NaN, 2.0, NaN, 3.0]}'
readings = json.loads(payload)["readings"]
print(deduplicate(readings))
# Expected: [1.0, nan, 2.0, 3.0]
# Actual: [1.0, nan, 2.0, nan, 3.0]
Two facts collide here, both individually well-known, but together devastating:
NaN != NaN. By design, no NaN compares equal to anything, including itself.x is candidate or x == candidate. The identity shortcut exists for speed and to make weird objects (like NaN) at least seem well-behaved in collections.In the unit test, every NAN in the list is literally the same Python object (id(NAN) is constant). When the loop hits the second NAN, the identity check v is seen_element returns True, and it's correctly skipped. The assertion also passes for the same reason — list equality is element-wise with the same identity-first rule.
In production, json.loads manufactures a fresh float('nan') object for every NaN token. Now each NaN in the list is a different object: identity fails, equality fails (NaN != NaN), and the "dedup" passes every NaN straight through. The deeper the pipeline, the worse it gets — every float(), numpy conversion, or arithmetic result that yields NaN produces a brand-new object.
The trap is especially nasty because the test looks like it covers the case. Reviewers see a NaN in the input and a deduplicated NaN in the expected output, and conclude the function handles NaN. It doesn't — it handles shared-identity NaN, which is an artifact of how the test was written, not a property of the data.
Canonicalize NaN explicitly. Either drop it, or replace every NaN with a single sentinel before the set check:
import math
def deduplicate(values):
seen = set()
unique = []
nan_seen = False
for v in values:
if isinstance(v, float) and math.isnan(v):
if nan_seen:
continue
nan_seen = True
unique.append(v)
elif v not in seen:
seen.add(v)
unique.append(v)
return unique
The same pattern bites dict keys, Counter, functools.lru_cache with float arguments, and anywhere "value equality" is silently doing identity equality for you. If your data can contain NaN, write a test where the NaNs come from different sources — parsing, arithmetic, conversion — not a shared module-level constant.
