AWS Lambda has a second kind of function. The headline is that you can run Express or Hono directly, but the sentence that changes how you write code is this one: “A single warm execution environment can serve multiple concurrent requests, which reduces cold starts by orders of magnitude.”
Lambda has spent a decade guaranteeing the opposite.
Executive summary
“Lambda Web Functions, a new serverless compute primitive where your code handles HTTP requests directly.” Your process listens on a port. You bring a normal web framework, “with no adapters or custom configuration”. Node.js only, five Regions, available now.
Everything you have today is renamed: “we are also renaming existing Lambda functions to Lambda Event Functions, a naming change only that requires no code changes or redeployments”.
The billing model is different too — “active CPU time” rather than wall-clock duration. But the concurrency change is the one that decides whether your existing code is safe to move.
What changed
| Aspect | Lambda Event Functions (what you have) | Lambda Web Functions (new) |
|---|---|---|
| Programming model | A handler invoked with an event | “your code handles HTTP requests directly”, listening on a port |
| Concurrency per environment | One request at a time | “A single warm execution environment can serve multiple concurrent requests” |
| Billing | Duration × memory | “active CPU time and the memory your environment holds” |
| Runtimes | Many | “Today, Lambda Web Functions supports the Node.js runtime” |
| Streaming | Response streaming, opt-in | “native HTTP streaming” |
Architecture
The old invariant is documented rather than folklore. The Lambda concurrency page states it directly: “Concurrency is the number of in-flight requests that your AWS Lambda Event Function is handling at the same time. For each concurrent request, Lambda provisions a separate instance of your execution environment.”
One request, one environment. That is why module scope has been safe to use as per-request storage — there was nothing else in there to collide with.
“A single warm execution environment can serve multiple concurrent requests” removes it.
What that does to code you already have
Module scope was effectively per-request private. It no longer is. Consider the shape that appears in a great deal of ordinary Node.js:
| In module scope | Event Function | Web Function |
|---|---|---|
| A database client or connection pool | Fine — reused across invocations | Fine, and the point of warm reuse |
| A config object loaded once | Fine | Fine — it is read-only |
| The current request's tenant ID | Fine — one request at a time | Shared. Another request can read it. |
| The authenticated user for this request | Fine | Shared |
| An open transaction or correlation ID | Fine | Shared |
The bottom three are not performance bugs. They are cross-request data leaks — one user's tenant context visible to another user's request — and they are the worst variety to find, because they need two requests in flight in the same environment at the same time. They will not reproduce at low load. They appear under traffic, intermittently, and look like impossible data.
AWS is accurate that the rename needs no changes: Event Functions keep the old model. The hazard is in the other direction, when somebody moves a working handler across because the announcement says frameworks work unchanged. The framework works unchanged. Code that assumed single-request isolation does not.
Business value
Real, and the cold-start claim is the headline for good reason: “reduces cold starts by orders of magnitude”. If one warm environment absorbs many concurrent requests, a traffic spike no longer means a proportional number of new environments — and a new environment is where a cold start comes from.
The billing change compounds it: “You pay for active CPU time and the memory your environment holds, not for idle CPU time your code spends waiting on databases or downstream services.”
That inverts the economics of an I/O-bound handler. A function that spends most of its duration awaiting a query has been paying full duration for that wait. Under the new model it is not. For the API-in-front-of-a-database shape — which is most of them — this is the more interesting half of the announcement, and it gets one sentence.
AWS names the target workloads: “AI inference endpoints, server-side rendered (SSR) web apps, streaming API endpoints, microservices, and webhooks”. The first two are the tell — both are long-running and I/O-heavy, and both have been awkward on Lambda precisely because of the duration billing and the one-request-per-environment model.
Security considerations
The shared-module-scope problem above is a security finding, not just a correctness one. A tenant identifier or an authenticated principal cached in a module-level variable becomes readable by a concurrent request from a different user. In a multi-tenant application that is the definition of a serious data-isolation failure, and nothing in the platform will flag it.
The remedy is ordinary web-server discipline: per-request context carried on the request object or in
AsyncLocalStorage rather than in module scope. That is normal practice for anyone who has written
Express for a long-lived server, and it is exactly the discipline Lambda has let people skip.
“Web Functions support Amazon Virtual Private Cloud (VPC) connectivity” is the other half worth noting — this is a real network-attached compute surface, not an isolated edge handler, and it should be treated as one when you decide what it may reach.
Cost considerations
Two dimensions, and the second is new: “active CPU time and the memory your environment holds”. Memory is billed on what the environment holds, not per invocation — which makes sense when one environment serves many requests, and means the per-request memory cost falls as concurrency per environment rises.
I am not putting a figure on the comparison. The announcement states the model and points at the pricing page for numbers, and a break-even between duration billing and active-CPU billing depends entirely on your I/O ratio. The honest guidance is to measure your own: a handler that is 80% awaiting a database will move very differently from one that is 80% computing.
Operational considerations
Your existing dashboards change meaning. AWS defines the metric against the old model — “Concurrency is the number of in-flight requests that your AWS Lambda Event Function is handling at the same time” — and that held because each concurrent request got its own environment. With several requests per environment the two stop being the same number, and alarm thresholds calibrated on the old relationship will read low.
“With multi-Region routing, you can deploy once and serve each request from the Region closest to the end user” is significant and underexplained in one line. Deploy-once multi-Region changes where your data needs to be, and a function serving from Tokyo against a database in Ireland is a latency problem the routing will not solve for you.
And the same-day companion: “Amazon CloudFront now supports AWS Lambda Web Functions as an origin”. That completes edge-to-application with no load balancer and no API Gateway in between, which is a genuinely simpler topology and one fewer place for a timeout setting to disagree.
Tradeoffs
| Choice | Gains | Costs |
|---|---|---|
| Move an existing handler to a Web Function | Cold-start and billing improvements immediately | Every single-request assumption in that code becomes a concurrency bug, silently |
| Write a new service as a Web Function | Normal framework, normal middleware, normal streaming | Node.js only today, and five Regions |
| Stay on Event Functions | Nothing changes — the rename is cosmetic | You keep paying for idle wait time |
| CloudFront straight to a Web Function | One less hop, one less timeout to reconcile | You lose whatever the removed layer was also doing — WAF placement, auth, request shaping |
Implementation guidance
- Before moving any existing code, grep for mutable module-scope state. Anything assigned per request outside the handler is now shared. This is the one step that cannot be skipped.
- Adopt
AsyncLocalStoragefor request context if you are carrying a tenant, user or correlation ID. It is the standard answer and it works the same way it does on any Node server. - Leave clients and pools in module scope. They are meant to be shared, and warm reuse is the benefit.
- Load-test with real concurrency. The failure mode needs two simultaneous requests in one environment; a sequential test suite will pass on broken code.
- Re-examine concurrency alarms — concurrent executions is no longer in-flight requests.
- Start with a new service, not a migration. The upside is available without taking on the audit of existing assumptions.
- Check the Region list — N. Virginia, Ohio, Oregon, Tokyo, Ireland. Five, and AWS points at the Capabilities by Region page for the current set.
Best practices
- Treat a Web Function as a web server, because it is one. Every habit that is correct on a long-lived Express process is correct here, and several habits that were safe on Lambda are not.
- Keep per-request state on the request. The platform no longer isolates it for you.
- Measure your I/O ratio before predicting the bill. Active-CPU billing helps a waiting handler and does little for a computing one.
- Do not read the rename as a deprecation. Event Functions are the same product with a new label, and AWS is explicit that nothing needs redeploying.
- Decide deliberately what you lose by removing a layer when putting CloudFront straight onto a Web Function.
Who should adopt this and when
Now, for a new I/O-bound HTTP service in a supported Region. Normal framework, better cold starts, and you are not paying for database wait. This is the case the feature was built for.
Now, for SSR and AI inference endpoints — AWS names both, and both have been poor fits for the old model for exactly the reasons this changes.
Carefully, for an existing Lambda you want to move. The gain is real and the prerequisite is an honest audit of module-scope state. If that audit is not going to happen, do not move it.
Not yet, if you are not on Node.js, or not in one of the five launch Regions.
Key takeaways
- “A single warm execution environment can serve multiple concurrent requests” — the one-request-per-environment invariant is gone, and module scope is no longer per-request private.
- That makes mutable module-scope state a cross-request data leak, which only appears under concurrent load.
- Billing moves to “active CPU time and the memory your environment holds”, so waiting on a database is no longer billed as it was.
- Existing functions are renamed Lambda Event Functions — label only, nothing to redeploy, old model intact.
- Node.js only; five Regions; native HTTP streaming, multi-Region routing and VPC connectivity included.
- CloudFront can now use a Web Function as an origin directly.
Official AWS references
- AWS Lambda announces Lambda Web Functions — the direct-HTTP programming model, the multiple-concurrent-requests statement, active-CPU billing, the Event Functions rename, runtime and Regional availability, streaming, multi-Region routing and VPC connectivity
- Amazon CloudFront now supports AWS Lambda Web Functions as an origin
- Understanding Lambda Event Function concurrency — the definition of concurrency as in-flight requests, and that each concurrent request gets a separate instance of the execution environment, which is the invariant Web Functions change
- AWS Lambda pricing — where the active-CPU rates live; no figures in this post are drawn from it
- AWS News Blog: Announcing AWS Lambda Web Functions — further reading with a worked example; no claims in this post are drawn from it
Comments