AWS Lambda is Amazon's event-driven compute service: you upload a function, AWS runs a copy of it for each incoming event, and you pay per invocation. There are no servers to provision or patch, and scaling from zero to thousands of concurrent executions is automatic. That is genuinely valuable — but "serverless" does not mean "zero operations," and Lambda has sharp edges worth knowing before you commit an architecture to it.

How It Works

Lambda functions are triggered by events: an API Gateway HTTP request, a file landing in S3, a message on SQS, an EventBridge schedule, a DynamoDB stream. Each concurrent event gets its own execution environment, so scaling is per-request rather than per-server. Officially supported runtimes cover Node.js, Python, Java, .NET, Go, and Ruby, plus custom runtimes and container images up to 10 GB for anything else.

One nuance the marketing skips: AWS patches the underlying OS and runtime, but you own the runtime version. Runtimes are deprecated on AWS's schedule — Node.js versions roll from 18 to 20 to 22, Python versions age out — and every deprecation forces a test-and-upgrade cycle across your function fleet. Teams with hundreds of functions feel this as a recurring operational tax.

The Pricing Model

Lambda bills two meters: requests (currently $0.20 per million) and compute duration in GB-seconds — memory allocation multiplied by execution time, at 1 ms granularity (about $0.0000167 per GB-second on x86, slightly less on Graviton). Memory is configurable from 128 MB to 10 GB, and CPU scales with it, so giving a function more memory often makes it faster and barely more expensive. The free tier covers one million requests and 400,000 GB-seconds per month. The corollary: Lambda is extremely cheap for low or spiky traffic and increasingly expensive for sustained high utilization, where an always-on container on Fargate or EC2 — often paired with Savings Plans — usually wins. Run the numbers at your real traffic before deciding.

Cold Starts and How to Mitigate Them

When a request arrives and no warm execution environment exists, Lambda must create one: download the code, start the runtime, run your initialization. This cold start ranges from tens of milliseconds for a small Python or Node.js function to multiple seconds for a heavyweight JVM or .NET application. At low traffic, a meaningful share of requests hit cold starts — we have written up a real p99 incident in our Lambda cold-start lesson learned.

Mitigations, in the order to try them: keep deployment packages small and initialization lazy; prefer lighter runtimes where practical; enable SnapStart (available for Java, .NET, and Python), which resumes functions from a pre-initialized snapshot and typically cuts cold starts by an order of magnitude — free for Java, priced per cache-and-restore for the others; and for hard latency SLOs, buy provisioned concurrency, which keeps environments warm but bills you even when idle.

When NOT to Use Lambda

  • Long-running work. The hard 15-minute timeout rules out big batch jobs — use Step Functions to orchestrate, or AWS Batch and Fargate to execute.
  • Sustained, predictable high traffic. Per-GB-second pricing loses to always-on containers once utilization is consistently high.
  • Strict low-latency p99s without paying for provisioned concurrency.
  • Long-lived connections. WebSockets and streaming sessions fight the execution model.
  • Portability requirements. Event formats, IAM wiring, and service integrations couple you to AWS; that is often a fine trade, but make it consciously.

For event glue, APIs with spiky traffic, and background processing, Lambda remains the pragmatic default on AWS — and deploying it well is a solved problem; see our guide to deploying Lambda from GitHub Actions with OIDC. If you are weighing serverless against containers for a new workload, our cloud computing practice does exactly this kind of architecture and cost assessment.