Lambda and Serverless
Lambda runs your function on demand in Firecracker microVMs, scaling one environment per concurrent request. Watch cold vs warm starts and concurrency scaling, then build serverless architectures with the right error handling for each invocation type.
🎯 What you'll learn
- Invocation types and event sources
- Execution environments: cold vs warm starts
- Concurrency, reserved and provisioned concurrency
- Architectures, error handling, packaging, cost and when not to use Lambda
📚 Before you start
1What Lambda Is
AWS Lambda runs your function in response to events without you managing servers. You upload code (zip or container image), choose memory (128 MB – 10 GB; CPU scales proportionally), set a timeout (≤ 15 min), and AWS runs as many copies as needed. You pay per request and per GB-second of execution (03-ec2/ compares the model with always-on instances).
// handler.ts — Node.js runtime
import type { APIGatewayProxyEventV2, APIGatewayProxyResultV2 } from "aws-lambda";
export const handler = async (event: APIGatewayProxyEventV2): Promise<APIGatewayProxyResultV2> => {
const id = event.pathParameters?.id;
return { statusCode: 200, headers: { "content-type": "application/json" }, body: JSON.stringify({ id }) };
};2Event Sources and Invocation Types
| Invocation | Who calls | Retries | Examples |
|---|---|---|---|
| synchronous | caller waits for the result | caller's responsibility | API Gateway, ALB, Function URL, SDK Invoke |
| asynchronous | Lambda queues the event internally | 2 retries, then on-failure destination / DLQ | S3 events, SNS, EventBridge |
| poll-based (event source mapping) | Lambda polls the source and invokes with batches | depends on source (redelivery / bisect batch) | SQS, Kinesis, DynamoDB Streams, Kafka |
For SQS, configure partial batch responses (ReportBatchItemFailures) so one bad record doesn't retry the whole batch (07-sqs_and_sns/ exercise 3).
3The Lifecycle: Cold and Warm Starts
first request (or scale-out) → COLD START: download code, start a new execution environment
(Firecracker microVM), init runtime, run your top-level code (INIT)
→ INVOKE handler
later requests → WARM: reuse the same environment, only INVOKE runs
idle for a while → environment frozen, then reclaimedEach execution environment handles one request at a time; concurrency = number of environments. Consequences:
- Put expensive setup (SDK clients, DB connection, config loading) outside the handler — it runs once per environment and is reused across warm invocations.
- Global state persists between invocations in the same environment — caches are fine, but never store per-request data globally.
- Cold starts add ~100 ms–1 s (more with large packages, VPC attachment used to add more, Java without SnapStart). Mitigations: smaller bundles (esbuild tree-shaking), lighter runtimes, provisioned concurrency (pre-initialized environments, billed hourly), SnapStart (Java/Python/.NET snapshot of the initialized environment).
4Concurrency and Scaling
- Account concurrency limit per region (default 1,000) shared by all functions.
- Reserved concurrency guarantees capacity for a function and caps it — a protection for downstream systems (e.g. a database that can take only 100 connections).
- Scaling rate: each function can add ~1,000 concurrent executions every 10 seconds.
- Throttling (
429 TooManyRequestsException) happens when limits are reached: synchronous callers get errors; async events are retried by Lambda for up to 6 hours.
Databases and Lambda: hundreds of concurrent environments each opening connections can exhaust a relational database → use RDS Proxy (connection pooling), DynamoDB, or cap reserved concurrency.
5Configuration Essentials
- Memory also buys CPU (1,769 MB ≈ 1 vCPU). More memory often makes functions faster and cheaper — measure (AWS Lambda Power Tuning).
- Timeout: slightly above p99, and below the caller's timeout (API Gateway max 29 s).
- Environment variables for config; secrets from Secrets Manager/SSM with caching (
18-secrets_manager_ssm_and_kms/). - Execution role: least-privilege IAM role the function assumes (
21-iam_deep_dive/). - VPC: attach only if the function must reach private resources (RDS, ElastiCache); it then needs NAT or VPC endpoints for AWS APIs.
- Architecture: arm64 (Graviton) is ~20% cheaper per GB-second.
Under the HoodFirecracker MicroVMs▾
Each execution environment runs inside a Firecracker microVM — a minimal KVM-based virtual machine monitor written in Rust that boots a stripped-down Linux kernel in ~125 ms with a few MB of overhead. Many microVMs from different customers share a physical worker host, with hardware-virtualization isolation between them (stronger than containers sharing a kernel). Your code's lifecycle maps onto it: Init (start the runtime, run top-level code, up to 10 s free on cold start for on-demand functions), Invoke (handler runs; the environment is frozen — CPU paused — between invocations, which is why background work after returning a response doesn't reliably run), Shutdown. A placement service decides which worker gets new environments, and the frontend routes each invocation to an idle warm environment for that function version when one exists — otherwise a cold start happens.
📋 Quick Reference (Part 1)
invocation: sync (API GW/ALB) · async (S3/SNS/EventBridge, retries + destinations) · poll (SQS/Kinesis/Streams batches)
cold start = new environment + init; warm = reuse → do setup outside the handler
1 request per environment at a time · reserved concurrency = guarantee + cap · RDS Proxy for SQL databases
memory = CPU · timeout < caller timeout · arm64 cheaper · VPC only when needed1Common Architectures
API: client → API Gateway (HTTP API) → Lambda → DynamoDB
async work: S3 upload → Lambda (thumbnail) → S3 | API → SQS → Lambda workers
events: service → EventBridge bus → rules → Lambda / Step Functions / SQS
streams: DynamoDB Streams / Kinesis → Lambda (batches, ordered per shard)
scheduled: EventBridge Scheduler (cron/rate) → Lambda
orchestration: Step Functions state machine calling Lambdas and AWS APIs directly (20-eventbridge_and_step_functions/)Serverless = no servers to manage, scale to zero, pay per use: Lambda, API Gateway, DynamoDB, SQS/SNS, EventBridge, Step Functions, S3, Aurora Serverless, Fargate (container-level).
2Writing Good Functions
- Small, single-purpose handlers; shared code in libraries/layers.
- Idempotent: async and poll-based invocations are at-least-once; use idempotency keys (e.g. Powertools Idempotency with a DynamoDB table —
distributed-systems/message-brokers/09-delivery_guarantees). - Validate input at the edge (API Gateway request validation or in the handler).
- Structured logs with correlation IDs, metrics via EMF, traces via X-Ray/OpenTelemetry — Powertools for AWS Lambda (TS/Python/Java/.NET) provides logger, metrics, tracer, idempotency, batch processing, parameters.
- Don't wait in Lambda: polling loops and long sleeps burn money; use Step Functions waits or SQS delays.
- Return early from API handlers; offload slow work to queues.
3Error Handling by Invocation Type
| Type | Failure behaviour | Configure |
|---|---|---|
| sync | error returned to caller | caller retries, proper status codes |
| async | 2 retries with backoff, then dropped unless configured | maximum_retry_attempts, maximum_event_age, on-failure destination (SQS/SNS/EventBridge/Lambda) |
| SQS | message becomes visible again; redrive to DLQ after maxReceiveCount | partial batch failures, visibility timeout ≥ 6 × function timeout |
| Kinesis/Streams | batch retried until success or expiry — blocks the shard | bisect_batch_on_function_error, maximum_retry_attempts, on-failure destination |
4Packaging and Deployment
- Bundle with esbuild (tree-shaken single file, minified) — smaller packages = faster cold starts.
- Versions (immutable snapshots) and aliases (
live→ version 42) enable safe rollouts: CodeDeploy can shift alias traffic gradually (canary 10% for 5 min → 100%) with automatic rollback on CloudWatch alarms. - IaC: SAM, CDK (
22-cloudformation_and_cdk/), Terraform (devops-cloud/terraform/), Serverless Framework. - Container images (up to 10 GB) for large dependencies (ML libraries).
5Cost Model
cost = requests × $0.20 per million + GB-seconds × ~$0.0000166667 (x86; arm64 ~20% less)
example: 50M requests/month, 120 ms avg, 512 MB → 50M × 0.12 s × 0.5 GB = 3,000,000 GB-s ≈ $50 + $10 requestsLambda is very cheap for spiky or low-to-moderate traffic; at sustained high throughput, containers (Fargate/ECS/EKS) or EC2 can be cheaper. Also count API Gateway, data transfer, CloudWatch Logs ingestion (often the surprise line item) and NAT gateway data processing for VPC functions.
6When Not to Use Lambda
- Long-running work (> 15 min) or steady CPU-heavy workloads → containers.
- Ultra-low, consistent latency requirements where cold starts hurt (or pay for provisioned concurrency).
- Workloads needing persistent connections per process (WebSockets via API Gateway WebSocket APIs is possible but different), large local state, or GPUs.
- Chatty access to relational DBs without RDS Proxy.
Under the HoodHow Event Source Mappings Poll SQS▾
For SQS, Lambda runs pollers on your behalf (you don't pay for them): they long-poll the queue, gather up to batch_size messages (or wait up to maximum_batching_window_in_seconds), and invoke your function synchronously with the batch. On success the poller deletes the messages; on error they stay invisible until the visibility timeout expires and are redelivered — or, with ReportBatchItemFailures, only the reported message IDs are left for redelivery. The number of pollers scales up with queue depth (adding concurrency over time) and down when invocations throttle or error, and maximum concurrency on the mapping caps how many concurrent invocations the queue can drive — the right knob to protect a downstream database, rather than reserved concurrency (which would throttle pollers and push messages back to the queue).
⚠️ Common Pitfalls
- Heavy initialization inside the handler — paid on every invocation.
- Non-idempotent async handlers — duplicate side effects.
- No DLQ/destination for async invocations — failed events silently dropped.
- Unbounded concurrency against a SQL database.
- Timeout longer than the API Gateway timeout — clients get 504 while the function keeps running.
- Huge bundles with the full AWS SDK and dev dependencies.
📋 Quick Reference (Part 2)
Powertools: logger · metrics (EMF) · tracer · idempotency · batch · parameters
async: retries + on-failure destination · SQS: partial batch failures, DLQ · streams: bisect batch
deploy: esbuild bundle · versions + aliases + CodeDeploy canary with alarm rollback
cost: requests + GB-s (+ API GW, logs, NAT) · avoid for >15 min or steady heavy load🧠 Key takeaways
- Invocations are synchronous (API Gateway), asynchronous (S3, SNS, EventBridge: built-in retries + destinations) or polled (SQS, Kinesis: event source mappings).
- Each environment serves one request at a time; new concurrency means cold starts (keep init light, use SnapStart/provisioned concurrency for latency-critical paths).
- Concurrency = rps × duration; reserve concurrency to protect downstream systems.
- Design idempotent handlers, report partial batch failures for SQS, and know when containers are cheaper (steady high load, long jobs).
🎤 Interview Q&A
Q1What are Lambda's strengths and limits?core▾
Strengths: no servers to manage, automatic scaling per request, pay per invocation and duration, rich event integrations. Limits: 15-minute max duration, cold starts, ephemeral environment, memory up to 10 GB, concurrency limits, and harder local debugging.
- Bad fit? Long-running, steady high-throughput or latency-critical workloads where containers are cheaper.
Q2What are cold starts and how do you reduce them?core▾
The latency of creating a new execution environment (download code, start runtime, run init code). Reduce with smaller packages, lighter runtimes, lazy initialization, provisioned concurrency, and SnapStart for Java/.NET/Python.
- Does VPC attachment still add big cold starts? Not since Hyperplane ENIs (2019).
Q3How does Lambda concurrency work?core▾
Each concurrent request uses one execution environment; account-level concurrency is limited (default 1,000 per region). Reserved concurrency guarantees and caps a function's concurrency, protecting downstream systems.
- What happens when throttled? Synchronous callers get 429; async events are retried.
Q4How do you handle failures for async and stream invocations?senior▾
Async: retries (2 by default), then on-failure destinations or a DLQ. SQS: failed messages return to the queue (use partial batch responses). Kinesis/DynamoDB streams: retries block the shard, so use bisect-on-error, max retry age and failure destinations.
- Why partial batch responses? So one bad message doesn't make the whole batch retry.
Q5How do you share code and manage config in Lambda?core▾
Layers or bundling for shared code, environment variables and Parameter Store/Secrets Manager (with caching) for config, and infrastructure as code (SAM, CDK, Serverless Framework, Terraform).
- Why cache secrets outside the handler? Init code runs once per environment, reducing latency and API calls.
🛠️ Practice exercises
Each exercise is a file in this topic folder. Code exercises are self-checking: run them and make the checks pass.
✅ Check yourself
Answer the questions — feedback is instant.