skip to content

When a development server swaps a changed server module in place, what happens to state that module held at module scope?

level: middleimportance: should knowfreq 50%

answer

  1. the process survives, the module does not
  2. module scope runs again on save
  3. leaked pools, emptied caches
  4. reuse via a process-wide key

basics

~20 s

Module-scope state is re-created: the fresh copy of the module runs its initialisation again, so any pool, cache or registry it built is new and empty, while the previous instance can survive unreferenced and still holding its resources.

solid answer

~50 s

Saving a server file makes the development server replace that module with a freshly executed copy without restarting the process. Anything the module created at module scope — a database connection pool, an in-memory cache, a registered listener, a scheduled timer — is created again by the new copy. The old instance is usually unreachable from your code but not necessarily closed, so after an hour of editing you can be holding a dozen pools and listeners while every in-memory cache looks mysteriously empty. A deployed process never behaves this way: it executes each module once at startup and keeps that instance for the life of the process. The usual defence is to stash such singletons on a process-wide object under a stable key, reuse an existing entry instead of constructing a second one, and release the old resource in whatever disposal callback the development server offers.

go deeper

for a junior

The key fact to carry: saving a server file re-runs that file's top-level code inside the same process. Anything created up there is created again.

for a middle

Explain both symptoms from the one mechanism — orphaned resources that are never closed, and shared structures that silently start empty — and describe the process-wide lookup that fixes them.

for a senior

Diagnose it from the outside: climbing connection counts during editing, handlers firing N times, a cache that is always warm locally and always cold under load. Then say which state belongs outside the process entirely.

for a principal

The deeper point is that in-process state is unverifiable in development by construction. Decide as a matter of policy which state may live in a module and which must live in an external store.

Module scope is the code that runs once when a module is first evaluated — the `const pool = createPool(...)` at the top of a file, the listener you register beside it, the cache object you allocate to be shared by every request. In a deployed process that "once" really is once: the module is evaluated during startup and the instance it built lives until the process exits. In a development server the same word means something much weaker. ## Why the state moves at all A development server's whole value is that an edit becomes visible without a restart. To achieve that on the server side it replaces the changed module — and usually the modules that import it — with newly executed copies inside the same running process. Executing a module means running its module scope again. So the save that you experienced as "the page refreshed" was also a constructor call for every resource that module owned. The **process** survives; the **module instance** does not. That split is the source of every symptom below. ## The two symptoms **Leak.** The replaced module's resources are not automatically released. A connection pool keeps its sockets open, a listener stays subscribed to the emitter it registered with, a timer keeps firing. Nothing in your code can reach them any more, which is exactly why nothing closes them. Editing a data-access file forty times in a morning can therefore exhaust a database's connection limit or fill the console with handlers firing several times per event. **Amnesia.** The opposite symptom on the same mechanism. An in-memory cache, a rate-limit counter, a warm-up flag or an accumulated registry starts empty again after every save. In development that looks like a cache that never goes stale and a counter that never trips — pleasant, and completely unlike the deployed behaviour where the same structure fills up and stays filled for as long as the process runs. ## What survives a save and what does not | Held where | After a save in development | In a deployed process | |---|---|---| | Module-scope object in the edited module | Re-created, old copy orphaned | Created once at startup | | Module-scope object in an untouched module | Usually kept, but not guaranteed | Created once at startup | | Value on a process-wide global object | Kept — the process did not restart | Kept for the process's life | | External store (database, cache server, file) | Kept — it was never in the process | Kept, and shared across instances | | Per-request local variable | Irrelevant; it never outlived the request | Same | The row that matters is the third. Because the process is not restarted, a value attached to a process-wide global survives the swap even though the module that created it was replaced. ## Making a singleton survive the swap The standard pattern follows directly from that table: 1. Look up the instance on a process-wide object under a stable, unique key. 2. If it is missing, construct it and store it there. 3. If it exists, reuse it and construct nothing. 4. If the development server offers a callback for "this module is being replaced", close the resource there so an orphan is never created in the first place. This costs a few lines and removes both symptoms at once: exactly one pool exists no matter how many times you save, and the cache keeps its contents across edits, which incidentally makes the development behaviour a slightly more honest model of the deployment. ## Why you should still not trust what you see Even with that pattern in place, module state in development is a poor predictor. In development the process is one process on your machine, restarted whenever you stop the server, with a handful of requests through it. A deployment runs the same module under sustained traffic, for days, and possibly in several instances at once that cannot see each other's memory. Anything that only works because there is exactly one long-lived copy of a structure is a design question the development server is structurally unable to ask you. So the rule of thumb is: use the pattern to stop the leak and the amnesia, because both waste your time; but treat any behaviour that depends on in-process state as unverified until it has run in the built output — and, for anything correctness-critical, put the state in an external store rather than in a module.

  • Why can an in-memory cache look perfectly correct in development and still be wrong after deployment?
    In development it is emptied every time you save the file that owns it, so entries never live long enough to go stale and the eviction path is never exercised. A deployed process keeps the same instance for days, so staleness, unbounded growth and eviction become real for the first time there.
  • Which kinds of module-scope work are most dangerous to re-run on every save?
    Anything that acquires or registers something outside the module: opening a connection pool or socket, subscribing to a message broker, installing a timer or signal handler, or writing a startup record. Each save adds another one, and nothing removes the previous.
  • Does storing the singleton on a process-wide object change anything for the deployment?
    Functionally almost nothing — the deployed process evaluates the module once, so the lookup finds nothing and constructs the instance exactly as before. The pattern costs one branch and is worth keeping so both environments follow the same path.

saying these in an interview costs you the question

  • Thinks saving a server file restarts the whole server process
  • Treats development module state as proof the deployment keeps one instance
  • Blames the database when connection counts climb during a long editing session
  • Believes an orphaned connection pool is closed the moment it is replaced
  • Puts a singleton at module scope and never checks how many exist