> ## Content Index
> Fetch the complete content index at: https://debugly.dev/llms.txt
> Use this file to discover other available public pages before exploring further.

# Reviewing the Module-Level Dict That Every Request Wrote To
- URL: https://debugly.dev/reviewing-module-level-mutable-state/
- Published: 2026-10-09T17:55:00.000Z
- Updated: 2026-10-17T05:55:32.000Z
- Description: The cache lived at import scope, so it survived across requests and across users, and one user's entry was served to another, and the leak stopped only when…
- Author: Rohit Bhadani
- Tags: Code Review, Python, Concurrency

It ended at the incident. It started at the appeared. Every hand-off in between did exactly what it was told, and this is the one that lied.

I have hit this enough times that I now check for it before I check for anything clever.

Strip the module-level mutable state down and one property makes it a leak rather than a cache: the state outlives the request, so it accumulates across users and across time, and the key that would isolate the entries is the thing nobody added.

This was a Flask application, and every long-lived process where a module is imported once and reused for every request behaves the same way.

## What the review saw

The review saw the cache, and the cache was the performance, and the performance was the good, and the good was the pass, because the cache is the legitimate optimisation, and the optimisation was the review's criterion, and the criterion was met, and the met was the miss, because the scope was the wrong and the scope is the not the syntax.

The review did not ask who else can read this, and the ask is the question, and the question is the every request, and the every request is the every user, and the every user is the leak, and the leak is the fix's target, and the target is the scope, and the scope is the review's question that nobody asked.

The module-level is the invisible because it looks like the constant, and the constant is the safe, and the safe is the assumption, and the assumption is the mutable, and the mutable is the written, and the written is the shared, and the shared is the incident, and the incident is the reason the mutability matters more than the location.

## Why it leaked across users

The key was the resource's identifier, and the identifier was the not the user's, and the not user's was the collision, and the collision was the first user's write, and the write was the cached, and the cached was the second user's read, and the read was the first user's data, and the data was the leak, and the leak was the incident.

The cache had the no expiry, and the no expiry was the forever, and the forever was the growth, and the growth was the memory, and the memory was the second problem, and the second problem was the restart's necessity, and the restart was the only clear, and the clear was the temporary, and the temporary was the incident's recurrence, and the recurrence was the fix's target.

This is the same missing dimension as [the cache key that forgot the locale](https://debugly.dev/reviewing-cache-key-missing-locale/), and the common root is the property, not the platform.

## The questions that catch it

**Who can read this state?** The answer is the every request in the process, and the every request is the every user, and the every user is the isolation's requirement, and the requirement is the key, and the key is the fix, and the question is the review's first.

**How long does it live?** The answer is the process's life, and the process's life is the unbounded, and the unbounded is the memory's growth, and the growth is the expiry's requirement, and the requirement is the fix, and the question is the review's second.

**Is the write thread-safe?** The answer is the no, and the no is the race, and the race is the corruption, and the corruption is the lock's requirement, and the requirement is the fix, and the question is the review's third, because the dict's concurrent write is the undefined.

**What happens when the process restarts?** The answer is the state's loss, and the loss is the inconsistency across the replicas, and the replicas are the different views, and the views are the fix's target, and the target is the external store, and the question is the review's fourth.

## The fix

**Moved the cache to the request scope or an external store.** The request scope was the isolation, and the isolation was the fix, and the external store was the shared across the replicas, and the shared was the consistent, and the consistent was the fix, and the fix was the Redis, and the Redis was the discipline.

**Keyed the cache on the user and the resource.** The key was the composite, and the composite was the isolation, and the isolation was the fix, and the fix was the one line, and the line was the discipline, because the resource identifier alone is the collision.

**Added the expiry and the size bound.** The TTL was the bounded life, and the bound was the memory, and the memory was the fix, and the fix was the LRU, and the LRU was the discipline, because the unbounded cache is the eventual restart.

**Guarded the write with a lock where the state stayed in process.** The lock was the race's prevention, and the prevention was the fix, and the fix was the threading lock, and the lock was the discipline, because the concurrent dict write is the undefined behaviour.

**Added a test with two users and the same resource.** The test was the collision's reproduction, and the reproduction was the catch, and the catch was the guard, and the guard was the prevention, and the prevention was the fix.

## If it happens again

A mutable object at module scope lives for the whole process, so it is shared by every request and every user, and it grows without bound. Move it to the request scope or an external store, key it on the user as well as the resource, add a TTL and a size bound, and guard concurrent writes.

The cache was a module-level dict keyed on the resource identifier, so the first user's data was served to the second user who asked for the same resource, and the leak only cleared when the process restarted. Name that one property in review and this class of bug gets hard to ship.

---

Published on [debugly.dev](https://debugly.dev/) by [Rohit Bhadani](https://debugly.dev/about/) | Founder of [Krova Cloud](https://krova.cloud/?ref=debugly.dev)