Why does Lambda initialization code placed outside the handler function only run once per environment, and how would you use that intentionally?
Short Answer
The execution environment itself — including any module-level code that ran when it was first initialized — persists and is reused across multiple invocations while it stays warm, per Lambda's execution-environment-reuse mechanism; only the handler function itself is invoked fresh each time. This means anything placed outside the handler (imports, SDK client construction, a database connection, a loaded configuration file) executes once when the environment is first created, and is then simply reused, unmodified, by every subsequent invocation that lands on that same warm environment — a deliberate optimization opportunity for anything expensive to set up but safe to reuse across invocations.
Detailed Explanation
Why it happens: an execution environment's initialization phase runs the function's module-level code once, as part of preparing that environment to be ready to handle invocations — this is distinct from, and happens before, any actual invocation of the handler function itself. Once initialized, the environment (including whatever state that module-level code created — variables, open connections, loaded data) persists in memory, and Lambda routes subsequent invocations to that same environment (while it stays warm) by simply calling the handler function again, without re-running the module-level initialization code a second time.
Deliberate uses of this:
AWS SDK client construction — creating an SDK client (a DynamoDB client, an S3 client) involves some setup overhead; constructing it once at module scope and reusing it across invocations avoids repeating that overhead on every single invocation, meaningfully reducing per-invocation latency for warm invocations compared to constructing a fresh client inside the handler every time.
Database/connection pooling — establishing a database connection has real latency cost; opening it once at module scope (rather than per-invocation) means only the first invocation on a given environment pays that connection-setup cost, with subsequent invocations on the same warm environment reusing the already-open connection directly.
Loading configuration or reference data once — if the function needs some relatively static reference data (a configuration file, a lookup table) that doesn't change per-invocation, loading it once at module scope avoids redundantly reloading it on every single invocation.
Caching computed or fetched values across invocations — anything expensive to compute or fetch that's safe to reuse (not invocation-specific) can be cached in a module-scope variable, benefiting every subsequent invocation on that same warm environment, though this benefit is inherently probabilistic — it only helps for invocations that happen to land on an already-warm environment, not for genuinely cold starts.
The caveat worth being deliberate about: since this reuse is tied to a specific execution environment (which Lambda can create multiple instances of for concurrent invocations, and which eventually get recycled), this optimization only benefits some fraction of invocations (the warm ones), not a guarantee across all traffic — and anything cached this way needs to be safe to share across multiple invocations potentially interleaved on the same environment (Lambda doesn't guarantee strict invocation isolation for module-scope state the way it isolates each invocation's handler execution).
Interview Follow-Up Questions
- What would go wrong if you cached invocation-specific (not genuinely reusable) data at module scope by mistake?
- How would you measure how much latency benefit this optimization is actually providing in production?
- How does this interact with Lambda SnapStart's approach to reducing cold-start latency?
Key Takeaways
- Module-level code runs once when an execution environment initializes, not once per invocation — the environment (and its module-scope state) persists and is reused across subsequent invocations while warm.
- Deliberately placing expensive, reusable setup (SDK clients, database connections, static reference data) outside the handler avoids repeating that cost on every warm invocation.
- This benefit is probabilistic, only applying to invocations landing on an already-warm environment, not a guarantee across all traffic.
- Anything cached at module scope needs to be safe to reuse across potentially multiple invocations sharing that same environment instance.
References
Related Questions
- What's the mechanism difference between how EC2's IMDS delivers credentials versus how Lambda delivers them to a function's environment?SuggestedAdvanced
- What idempotency key design would you use for a payment-related Lambda function, given the higher stakes of a duplicate execution?SuggestedAdvanced
Last updated August 21, 2026 · Last reviewed August 21, 2026