mutated × shared — the answer changes between runs

Stale-Cache Heisenbug

Caching a result that depends on hidden mutable state serves the old answer forever — nobody invalidated it.

01the recipe

In the wild

example.py
# SMELL: cache a value that depends on hidden, mutable state.
# (cache-invalidation x impure-functions)
_cache = {}
def price(item):
    if item not in _cache:
        _cache[item] = item.base * TAX_RATE   # TAX_RATE: a mutable global
    return _cache[item]

TAX_RATE = 1.2     # later, far away, this changes -- the cache never hears

# RIGHT: make the function pure; key on every input.
def price(item, tax):
    return item.base * tax        # same inputs -> same output
The first call freezes a value computed from a global that later moves. Whether you see fresh or stale output depends on call order and cache warmth.
// observed
stale: first call wins; later TAX_RATE changes ignored
right: result tracks its inputs, every call
02weakness catalog

Mapped weaknesses (CWE)

On its own, this defect is catalogued by MITRE as one or more of these weaknesses. The exploitable vulnerability usually appears only when it chains or combines with another.