Node 20 is the right default for I/O-bound APIs and most glue. It stops being the right default when every millisecond of CPU in a hot transform shows up on the bill and p99. Rust custom runtimes (or provided.al2023 with a Rust binary) can cut duration sharply — but only after you measure cold start, ops cost, and team velocity honestly.
⚡ TL;DR: Benchmark wall duration, INIT, memory, and $/M invokes on production-shaped payloads; prefer Rust for pure CPU transforms with stable interfaces; keep Node for orchestration and SDK-heavy paths; don’t rewrite for fashion. Pair with Lambda Power Tuning and Lambda Cold Starts Node 20.
When Rust wins
| Workload | Node | Rust |
|---|---|---|
| JSON glue + AWS SDK calls | ✅ | Usually overkill |
| Image/PDF CPU transforms | Painful | ✅ |
| Tight crypto / compression loops | Slow | ✅ |
| Rapid product iteration | ✅ | Slower hiring/review |
// src/main.rs — provided.al2023 custom runtime sketch
use lambda_runtime::{service_fn, Error, LambdaEvent};
use serde::{Deserialize, Serialize};
#[derive(Deserialize)]
struct Input { payload: String }
#[derive(Serialize)]
struct Output { bytes: usize, hash: String }
async fn function_handler(event: LambdaEvent<Input>) -> Result<Output, Error> {
let compressed = compress_cpu_bound(&event.payload)?; // CPU wins here
Ok(Output { bytes: compressed.len(), hash: blake3_hex(&compressed) })
}
#[tokio::main]
async fn main() -> Result<(), Error> {
lambda_runtime::run(service_fn(function_handler)).await
}
Benchmark protocol (don’t skip)
# Same memory sizes, same payload corpus, arm64 vs x86_64
# 1) power-tune both runtimes
# 2) capture INIT duration vs Duration separately
# 3) compute cost = (duration_ms/1000 * memory_gb * rate + request_price) * 1e6
// scripts/compareCost.ts
export function costPerMillion(durationMs: number, memoryMb: number, gbSecondPrice: number, reqPrice: number) {
const gbSeconds = (durationMs / 1000) * (memoryMb / 1024);
return gbSeconds * gbSecondPrice * 1_000_000 + reqPrice * 1_000_000;
}
❌ Declaring victory from a laptop MicroBench that never hits S3 or the VPC — cold start and Hyperplane ENI realities dominate some paths (VPC cold start).
Hybrid architecture
Keep a Node orchestrator Lambda and offload the CPU kernel to Rust (or a container image) behind a stable contract:
API → Node handler (auth, validate, idempotency)
→ invoke Rust worker alias (CPU)
→ Node writes results / metrics
This preserves team speed where it matters and pays Rust only where profiles prove it.
Closing checklist
✅ Dos
– ✅ Profile production payloads before rewriting
– ✅ Compare arm64 Graviton for both stacks
– ✅ Pin custom runtime images/layers; CI build reproducibly
– ✅ Keep orchestration in Node if SDK ergonomics matter
– ✅ Re-run power tuning after the rewrite
❌ Don’ts
– ❌ Don’t rewrite I/O-bound CRUD to Rust for “performance”
– ❌ Don’t ignore INIT regressions from large binaries
– ❌ Don’t skip load tests on concurrency + DNS/SDK
– ❌ Don’t strand the team without Rust review capacity
Related reading
- Lambda Power Tuning Memory Optimization
- AWS Lambda Arm64 Graviton3 Performance
- Lambda Cold Starts Node 20
- Lambda Container Images vs Zip
Last updated on September 11, 2026
Discover more from CheatCoders
Subscribe to get the latest posts sent to your email.

Pingback: Python Free-Threading: What Breaks When the GIL Becomes Optional - CheatCoders