Direct, SQS, EventBridge or Step Functions? What 160 Lambda Benchmarks Say
27 Sept 2026 · 6 min read

Most teams choose how to chain their Lambda functions by habit. Step Functions because the docs recommend it, EventBridge because it feels decoupled, a queue because that’s what the last project used. I did it too, for years.
So for my MSc in Software Engineering I measured it instead. I recently finished, with a distinction, and the results changed how I design serverless systems. This post is the practical version: what I tested, what surprised me, and the rules I use now.
If you’d rather skip to the answer, the decision tool applies everything below to your workload.
What I tested
Four ways to chain five Lambda functions together:
- Direct invocation: each function calls the next in code.
- Queue (SQS): each function drops a message for the next.
- Event-driven (EventBridge): each function publishes an event a rule routes onwards.
- Step Functions: a state machine runs the chain.
Each chain ran two kinds of work. A CPU-bound task (a Fibonacci busy loop) and an I/O-bound task (waiting on a simulated external API). I varied memory from 128 MB to 3,008 MB and concurrency from 1 to 50, and ran everything with and without Provisioned Concurrency.
Every resource was defined in AWS SAM and every run sent a fixed number of requests, so strategies compared fairly. That came to 160 run summaries of latency, billed duration, cold starts and cost.
Finding 1: managed orchestration charges a routing tax
This was the headline. EventBridge and Step Functions add roughly 1.5 seconds to a chain that direct invocation and SQS don’t pay. It isn’t your code; it’s the platform routing work between your functions.
| Strategy | Mean latency | Cost per million requests |
|---|---|---|
| Direct invocation | 553 ms | $0.88 |
| Queue (SQS) | 561 ms | $0.86 |
| Step Functions | 1.31 s | $1.78 |
| EventBridge | 2.09 s | $2.44 |
The gap held for I/O-bound work too, and it didn’t care what the functions were doing. In a separate cross-check on CPU-bound work, direct invocation was 52.6% faster and 43.3% cheaper than Step Functions.
Step Functions has a second cost that hides until scale: it bills per state transition, so a five-step chain pays at least five transition fees on every run, however quick the functions are.
The routing tax sets a floor on how small your functions should be. If a function runs for 50 ms and the hop to the next one costs 1,500 ms, the platform spends thirty times longer moving work than doing it. Under about half a second of work per function, heavy orchestration is the wrong tool; merge the functions or chain them directly.
Finding 2: memory has a sweet spot, and it depends on the work
Lambda gives you CPU in proportion to memory, so more memory usually means faster functions. For CPU-bound work, up to a point:
| Memory | Mean latency | Cost per million requests |
|---|---|---|
| 128 MB | 738 ms | $1.90 |
| 512 MB | 415 ms | $1.76 |
| 1,024 MB | 243 ms | $1.86 |
| 3,008 MB | 219 ms | $3.03 |
512 MB was the cheapest configuration of all, and 44% faster than 128 MB. Past about 1.8 GB the function gets a full vCPU, and a single-threaded Node.js function can’t use a second one: going from 1,792 MB to 3,008 MB bought 23 ms and cost 18% more.
I/O-bound work was the opposite. Latency sat at about 1.3 seconds at every memory size, because the function spends its time waiting on the network, not computing. More memory only raised the price: 3,008 MB cost around 19 times as much as 128 MB for the same speed.
So there’s no single right memory setting. Give CPU-bound functions 512 MB and I/O-bound functions the minimum. Over-provisioning “to be safe” is pure waste on network-heavy functions, in money and in energy.
Finding 3: queues catch up
I expected SQS to be the slow, resilient option. At low concurrency it was: with one request at a time, a queued chain took 1.9 seconds against 1.7 for direct invocation.
At 50 concurrent executions, it finished within 0.6% of direct invocation. AWS’s pollers scale out to meet the traffic, and once they’re warm the queue costs almost nothing in latency while giving you buffering, retries and dead-letter queues.
That’s the reason SQS is my default now. It’s nearly as fast and cheap as calling functions directly, and it doesn’t fall over when a downstream service does.
Finding 4: Provisioned Concurrency is a capacity, not a switch
Provisioned Concurrency keeps functions warm. It worked: cold starts fell from 39% of requests to 3 to 4%.
The remaining cold starts weren’t bad luck. I’d provisioned 10 instances, and bursts of 100 requests overflowed them onto ordinary on-demand instances, cold starts and all. Provisioned Concurrency only protects traffic up to the capacity you buy, so size it to your peak burst, not your average.
The cost can invert, too. At low volume, paying for idle warm instances raised unit costs two to three times; with EventBridge, cost per million requests went from $0.86 to $6.10.
Paired with a queue, though, it shone. The queue smooths bursts so the warm instances stay busy without overflowing: latency fell 87.5% (5.1 s to 0.64 s) and cost per million requests halved.
The decision framework
Put together, the results give a short set of rules:
| When you want | Use | The catch |
|---|---|---|
| The lowest latency or cost | Direct invocation (512 MB for CPU work, 128 MB for I/O) | No backpressure: one failure stops the chain |
| Resilience and control | SQS | Pollers need to be warm |
| Many consumers for one event | EventBridge | A ~1.5 s tax, so never for linear chains |
| Auditing and complex branching | Step Functions | The highest cost and a ~1.5 s floor |
Default to the simple patterns. Reach for EventBridge or Step Functions when the workflow genuinely needs fan-out or visual state, and treat the extra 1.5 seconds and the bill as what you’re paying for that.
Try the framework on your own workload →, or review a whole workflow hop by hop and take away a decision record for your team.
Where this stops
It’s AWS only. The routing tax is an implementation detail of EventBridge and Step Functions; Azure Durable Functions and Google Cloud Workflows need their own measurements. I tested linear chains up to 50 concurrent executions, not complex branching or thousands of users, and the numbers assume warm infrastructure. Sporadic traffic can leave queues and event buses cold.
What the research taught me
The best lesson wasn’t in the numbers. Twice, my first results looked like serverless being flaky: a huge latency spike for queues at moderate load, and Provisioned Concurrency apparently failing. Both turned out to be real mechanisms, throttled pollers and capacity overflow, and both became findings.
An odd result is usually a system telling you something. And because the whole environment was infrastructure as code, re-running a test group to check a theory took minutes rather than days. That’s the part I’ve carried straight back into everyday engineering.


