AWS Lambda Execution Environment Lifecycle
An AWS Lambda execution environment is the isolated runtime that AWS creates to run your function code. It loads the runtime and your code, handles one invocation at a time, and may be reused for later invocations.
This lifecycle explains two familiar performance patterns: a cold start, where Lambda must create and initialize an environment, and a warm invocation, where an existing environment handles the request without repeating that setup.
Why Lambda reuses environments
Without reuse, every request would need a new operating environment, language runtime, code package, and application setup. That would make serverless functions simpler to operate, but it would add startup work to every request.
Instead, Lambda keeps an environment available for some time after an invocation. If another request can use it, Lambda sends the request to that environment. The initialization cost is then paid once rather than once per request.
Reuse is an optimization, not a promise. Lambda can create or discard environments at any time. Your function must work correctly when every invocation starts in a completely new environment.
Initialization: the cold-start path
When Lambda needs a new environment, it runs an initialization phase before calling your handler. A handler is the function entry point that receives an event and returns a result.
Initialization broadly includes:
- Starting the language runtime, such as Python, Node.js, Java, or .NET.
- Loading your deployed function code and configuration.
- Loading external libraries imported by that code.
- Running code at module or class scope—code outside the handler.
- Starting any configured Lambda extensions, which are companion processes that integrate with the function environment.
For example:
import boto3
# Runs during initialization, not once per invocation.
s3 = boto3.client("s3")
def handler(event, context):
return {"statusCode": 200}
The boto3.client call is outside handler, so it normally runs when the module is first loaded. The first request pays for importing the library and creating the client. Later requests in the same environment can use the existing client.
This is why logs often show an INIT_REPORT entry before the first invocation, or why a tracing system labels the first request as a cold start. Initialization time is also reflected in the duration reported for a cold invocation, depending on the runtime and the reporting view.
The environment is considered ready only after initialization completes. If initialization fails, Lambda cannot invoke the handler in that environment and may create another one for a later attempt.
Invocation and reuse
Lambda then enters the invoke phase: it calls the handler with an event and waits for the result. An environment handles only one invocation at a time, but Lambda can create multiple environments when requests arrive concurrently.
After a successful response, Lambda does not normally erase the process immediately. It may retain the environment and route a later request to it. That later request skips runtime startup, imports, and other initialization work. This is the warm path.
The following state can therefore survive between invocations in the same environment:
- Module-level and global variables in the running process.
- Initialized library objects, such as SDK clients, connection pools, and caches.
- Files in
/tmp, Lambda's temporary writable filesystem for the environment. - Open network connections, if the operating system and remote service still consider them usable.
A global variable is not shared across all Lambda invocations or all instances of a function. It belongs only to one environment. If Lambda creates five environments, there may be five separate copies of that variable.
cache = {}
def handler(event, context):
key = event["key"]
if key not in cache:
cache[key] = load_value(key)
return cache[key]
This cache can make warm requests faster, but it is only a best-effort cache. A new environment starts with an empty dictionary, and each environment has its own dictionary. It must not be the only place where required data is stored.
Freeze and thaw
When Lambda finishes an invocation and decides to retain the environment, it can freeze it. Freezing suspends the runtime and extension processes, preserving their in-memory state rather than continuing to run them while no request is being handled.
When Lambda later routes a request to that environment, it thaws it: the suspended processes resume, and Lambda invokes the handler again. Module-level code does not generally run again during thaw. The process resumes with the globals, objects, and in-memory caches it had when it was frozen.
This is the key distinction:
Reuse resumes an existing process; it does not create a fresh process and rerun initialization.
Code that starts background work during an invocation should not depend on that work continuing after the response. A frozen environment pauses such work, and Lambda may discard the environment instead of thawing it. Perform required work before returning, and treat post-response activity as unreliable.
Connections require similar caution. A connection may survive a freeze and be useful after thaw, but it may also have been closed by a database, load balancer, or network device while the environment was idle. Reuse a connection only after handling stale-connection errors, or recreate it when necessary.
Shutdown and the limits of persistence
Eventually Lambda may shut an environment down. It can also replace environments after a deployment or configuration change, and it can remove idle environments as capacity changes. The /tmp directory, globals, caches, and open connections disappear with that environment.
Lambda does not provide an application-level notification that lets you rely on a graceful shutdown for essential work. Durable state belongs in a service designed to store it, such as a database or object store—not in process memory or /tmp.
What reuse means for performance
Cold-start time is the cost of creating and initializing one environment. It is affected by runtime choice, package size, dependency imports, extensions, and work performed at module scope. Warm invocations avoid most of that cost, so their latency usually consists mainly of handler work and its downstream calls.
A useful design pattern is to initialize reusable clients and immutable configuration outside the handler, while keeping request-specific state inside it. This can reduce warm-path latency without accidentally carrying one request's data into another.
Do not, however, optimize as though every request will be warm. Traffic spikes, parallel requests, deployments, and environment retirement all create new environments. Measure both cold and warm paths, keep initialization bounded, and make correctness independent of reuse. Lambda's environment lifecycle is a performance opportunity—not a storage or consistency guarantee.