AWS Lambda Deep Dive
A comprehensive deep dive into AWS Lambda — execution environments, cold starts, invocation types, concurrency, VPC integration, layers, destinations, and deployment patterns for the DVA-C02 exam.
What is AWS Lambda?
AWS Lambda is AWS's serverless compute service — it runs your code in response to events without requiring you to provision or manage servers. You supply code as a ZIP archive or container image, configure triggers, and Lambda handles everything: server provisioning, OS patching, capacity scaling, and high availability.
Lambda is billed in 1 ms increments, only while your code is running — zero charge during idle time.
Core mental model: Lambda inverts traditional compute. Instead of "How many servers do I need?" you ask "What events trigger my code and for how long does it run?"
Execution Environment Lifecycle
Every Lambda invocation runs inside an execution environment — an isolated micro-VM managed entirely by AWS. Understanding this lifecycle is the single most important concept for writing performant Lambda functions.
Phase 1 — INIT (Cold Start)
When Lambda creates a new execution environment, the INIT phase runs. This is what we call a "cold start." It has three sequential sub-phases:
| Sub-phase | What happens | Time budget |
|---|---|---|
| Extension init | Load Lambda extensions | 10 seconds |
| Runtime init | Bootstrap the language runtime (Node.js, Python, JVM…) | 10 seconds |
| Function init | Execute module-scope code outside your handler function | 10 seconds |
The Function init sub-phase is where YOUR initialization code runs — SDK clients, database connections, config loading. This is the primary optimization opportunity.
1// ✅ INIT PHASE — runs ONCE per execution environment lifecycle
2const { DynamoDBClient } = require('@aws-sdk/client-dynamodb');
3const { DynamoDBDocumentClient, GetCommand } = require('@aws-sdk/lib-dynamodb');
4
5// Initialize expensive resources here — reused across warm invocations
6const ddbClient = new DynamoDBClient({ region: process.env.AWS_REGION });
7const docClient = DynamoDBDocumentClient.from(ddbClient);
8const CONFIG = JSON.parse(process.env.APP_CONFIG ?? '{}');
9
10// ✅ INVOKE PHASE — runs on EVERY invocation
11exports.handler = async (event) => {
12 // ddbClient and CONFIG are already initialized — zero overhead on warm starts
13 const result = await docClient.send(new GetCommand({
14 TableName: 'Users',
15 Key: { userId: event.pathParameters.userId },
16 }));
17 return { statusCode: 200, body: JSON.stringify(result.Item) };
18};Phase 2 — INVOKE
Lambda calls your handler function with the incoming event. After the handler returns, the execution environment stays alive and warm, ready for the next invocation. Module-scope state persists across warm invocations on the same environment.
1// ⚠️ Module-scope state persists between warm invocations on the same environment
2let requestCount = 0;
3
4exports.handler = async (event) => {
5 requestCount += 1;
6 console.log(`Warm invocation #${requestCount} on this environment`);
7 // Never rely on this for business logic — any invocation may hit a fresh environment
8};Phase 3 — SHUTDOWN
When Lambda terminates an idle execution environment, extensions receive a SHUTDOWN event and have 2 seconds to flush telemetry and clean up connections.
Lambda Handler Anatomy
Node.js Handler
1exports.handler = async function(event, context) {
2 console.log('Event:', JSON.stringify(event));
3 console.log('Request ID:', context.awsRequestId);
4 console.log('Remaining time (ms):', context.getRemainingTimeInMillis());
5 console.log('Memory limit (MB):', context.memoryLimitInMB);
6
7 return {
8 statusCode: 200,
9 body: JSON.stringify({ message: 'Hello from Lambda!' }),
10 };
11};Python Handler
1import json
2
3def handler(event, context):
4 print(f"Request ID: {context.aws_request_id}")
5 print(f"Remaining time: {context.get_remaining_time_in_millis()}ms")
6 print(f"Memory limit: {context.memory_limit_in_mb}MB")
7
8 return {
9 'statusCode': 200,
10 'body': json.dumps({'message': 'Hello from Lambda!'})
11 }The Context Object — Key Properties
| Property (Node.js) | Property (Python) | Description |
|---|---|---|
awsRequestId | aws_request_id | Unique ID for this invocation |
functionName | function_name | Lambda function name |
memoryLimitInMB | memory_limit_in_mb | Configured memory size |
getRemainingTimeInMillis() | get_remaining_time_in_millis() | Milliseconds before timeout |
logGroupName | log_group_name | CloudWatch Logs group |
invokedFunctionArn | invoked_function_arn | Full ARN including alias/version qualifier |
Configuration Limits Reference
| Setting | Default | Maximum |
|---|---|---|
| Execution timeout | 3 seconds | 15 minutes (900 s) |
| Memory | 128 MB | 10,240 MB |
| Ephemeral storage (/tmp) | 512 MB | 10,240 MB |
| Deployment package (ZIP) | — | 50 MB compressed / 250 MB uncompressed |
| Container image size | — | 10 GB |
| Concurrent executions | 1,000 / region | Increase via support ticket |
| Environment variables | — | 4 KB total |
| Layers per function | — | 5 layers |
CPU scales with memory, not configured directly. At 1,769 MB your function has exactly 1 full vCPU. At 3,538 MB it has 2 vCPUs. For CPU-bound workloads (image processing, ML inference), increasing memory is the only way to add CPU.
Invocation Types
Lambda has three distinct invocation models. Knowing which services use which model is a DVA-C02 exam staple.
1. Synchronous — RequestResponse
The caller waits for Lambda to finish and receives the full response. On error, the exception is returned directly to the caller — no automatic retry.
Services that invoke synchronously: API Gateway, ALB, Cognito Lambda triggers, Step Functions Task states, CloudFront/Lambda@Edge.
1{
2 "httpMethod": "POST",
3 "path": "/orders",
4 "queryStringParameters": { "source": "web" },
5 "headers": { "Authorization": "Bearer eyJhbGci..." },
6 "body": "{\"product\": \"widget\", \"qty\": 3}",
7 "requestContext": { "requestId": "abc-123", "stage": "prod" }
8}1// Lambda response format for API Gateway proxy integration
2exports.handler = async (event) => {
3 const body = JSON.parse(event.body ?? '{}');
4 return {
5 statusCode: 201,
6 headers: { 'Content-Type': 'application/json' },
7 body: JSON.stringify({ orderId: 'ORD-' + Date.now(), ...body }),
8 };
9};2. Asynchronous — Event
The caller sends the event and immediately receives 202 Accepted — no waiting. Lambda queues the event internally and processes it in the background.
Services that invoke asynchronously: S3 event notifications, SNS, EventBridge rules, IoT rules.
Automatic retry behavior on function failure:
| Attempt | Timing |
|---|---|
| 1st | Immediately |
| 2nd | ~1 minute after 1st failure |
| 3rd | ~2 minutes after 2nd failure |
| After 3rd failure | Routed to Destination/DLQ, or discarded |
Events are retained in the Lambda internal queue for up to 6 hours before being discarded.
1{
2 "Records": [{
3 "eventSource": "aws:s3",
4 "eventName": "ObjectCreated:Put",
5 "s3": {
6 "bucket": { "name": "my-uploads-bucket" },
7 "object": { "key": "uploads/photo.jpg", "size": 2097152 }
8 }
9 }]
10}3. Poll-Based — Event Source Mapping
Lambda polls stream and queue services on your behalf. You configure an event source mapping; Lambda manages the polling loop, batching, scaling, checkpointing, and retry logic.
Supported sources:
| Source | Type | Ordering |
|---|---|---|
| Amazon SQS Standard | Queue | Best-effort |
| Amazon SQS FIFO | Queue | Strict per message group |
| Amazon Kinesis Data Streams | Stream | Per shard |
| Amazon DynamoDB Streams | Stream | Per shard |
| Amazon MSK / Kafka | Stream | Per partition |
1{
2 "Records": [{
3 "messageId": "19dd0b57-1234-4f67-8a7b-ce3b2d0c5d2f",
4 "body": "{\"orderId\": \"ORD-001\", \"amount\": 49.99}",
5 "attributes": { "ApproximateReceiveCount": "1" },
6 "eventSource": "aws:sqs"
7 }]
8}Partial batch failure — return only the IDs of failed messages so successful ones are not reprocessed:
1exports.handler = async (event) => {
2 const batchItemFailures = [];
3
4 for (const record of event.Records) {
5 try {
6 await processOrder(JSON.parse(record.body));
7 } catch (err) {
8 console.error(`Failed: ${record.messageId}`, err.message);
9 batchItemFailures.push({ itemIdentifier: record.messageId });
10 }
11 }
12
13 return { batchItemFailures };
14 // Requires FunctionResponseTypes: ['ReportBatchItemFailures'] on the event source mapping
15};Concurrency Deep Dive
Concurrency = the number of Lambda execution environments processing requests simultaneously.
Concurrency = Requests per second × Average duration (seconds)
Example: 200 req/sec × 0.3 sec = 60 concurrent executions
Account-Level Concurrency
Every AWS account has a default regional limit of 1,000 concurrent executions, shared across ALL Lambda functions in that region. When the limit is hit, synchronous callers receive TooManyRequestsException (HTTP 429) and asynchronous invocations are queued.
Reserved Concurrency
Reserved Concurrency guarantees AND caps concurrency for a specific function — it carves a dedicated slice from the regional pool:
Setting reserved concurrency to 0 disables the function (all invocations are throttled). This is a useful emergency circuit-breaker.
Provisioned Concurrency
Provisioned Concurrency pre-initializes execution environments so they respond to invocations instantly, with zero cold start latency.
| Model | Cold Start? | Cost |
|---|---|---|
| Default (unreserved) | Yes, on scale-out | Per invocation + duration |
| Reserved only | Yes, on scale-out | Per invocation + duration |
| Provisioned | No | Per provisioned env-hour + invocation + duration |
1# Enable Provisioned Concurrency on the "prod" alias
2aws lambda put-provisioned-concurrency-config \
3 --function-name MyFunction \
4 --qualifier prod \
5 --provisioned-concurrent-executions 100SnapStart (Java 11/17/21+): Lambda snapshots a fully initialized Java execution environment. Future cold starts restore from this cached snapshot — up to 90% cold start reduction without the ongoing cost of Provisioned Concurrency.
Cold Start Deep Dive
A cold start = INIT phase duration added on top of your handler execution time. For synchronous user-facing APIs, this is often the most impactful latency problem.
Root Causes and Mitigations
| Factor | Typical impact | Mitigation |
|---|---|---|
| Runtime bootstrap | Node.js/Python: 100–300ms; JVM: 500ms–3s | Use Node.js or Python for latency-sensitive paths |
| Package size | +10–50ms per MB of extra dependencies | Tree-shake; import only what you need |
| Initialization code | Proportional to work done | Lazy-load non-critical modules |
| VPC (old ENI model) | Added 10+ seconds | Modern Hyperplane ENIs: negligible impact |
Code Patterns That Reduce Cold Start Time
1// ❌ ANTI-PATTERN: Import entire AWS SDK v2 (loads all ~700 service clients)
2const AWS = require('aws-sdk');
3
4// ✅ BEST PRACTICE: AWS SDK v3 — modular, import only what you need
5const { S3Client, GetObjectCommand } = require('@aws-sdk/client-s3');
6const s3 = new S3Client({}); // Initialized ONCE in module scope
7
8// ❌ ANTI-PATTERN: Require heavy module inside handler (runs every invocation)
9exports.handler = async (event) => {
10 const sharp = require('sharp');
11 return await sharp(event.imageData).resize(800, 600).toBuffer();
12};
13
14// ✅ BEST PRACTICE: Lazy-load and cache heavy modules in module scope
15let sharp;
16exports.handler = async (event) => {
17 if (!sharp) sharp = require('sharp'); // Load once, cached for warm invocations
18 return await sharp(event.imageData).resize(800, 600).toBuffer();
19};Lambda in VPC
By default, Lambda runs in an AWS-managed network with full internet access. To reach resources in your private VPC (RDS, ElastiCache, internal APIs), configure VPC settings on the function.
Hyperplane ENIs are shared across functions using the same subnet + security group pair, eliminating the 10+ second ENI creation delay that affected older Lambda VPC configurations.
Required IAM Permissions
The Lambda execution role must have network interface permissions. Use the AWS managed policy AWSLambdaVPCAccessExecutionRole, which grants:
1{
2 "Effect": "Allow",
3 "Action": [
4 "ec2:CreateNetworkInterface",
5 "ec2:DescribeNetworkInterfaces",
6 "ec2:DescribeSubnets",
7 "ec2:DeleteNetworkInterface",
8 "ec2:AssignPrivateIpAddresses",
9 "ec2:UnassignPrivateIpAddresses"
10 ],
11 "Resource": "*"
12}VPC + Internet: VPC-connected Lambda functions lose internet access by default. To reach both your private VPC resources AND the public internet (Stripe, Twilio, etc.), place the function in a private subnet and route outbound traffic through a NAT Gateway in a public subnet.
Security Group Setup
Lambda SG outbound: TCP 5432 → RDS Security Group
RDS SG inbound: TCP 5432 from Lambda Security Group
Lambda Layers
Layers are ZIP archives that Lambda extracts to /opt before your function code runs. They enable sharing libraries, custom runtimes, and configuration across multiple functions.
Constraints:
- Maximum 5 layers per function
- Function code + all layers uncompressed ≤ 250 MB
- Layers are versioned and immutable — publish a new version to update
1# Build a Node.js dependency layer
2mkdir -p layer/nodejs && cd layer/nodejs
3npm install lodash axios
4cd ../.. && zip -r deps-layer.zip layer/
5
6# Publish the layer
7aws lambda publish-layer-version \
8 --layer-name common-nodejs-deps \
9 --description "Shared Node.js dependencies" \
10 --zip-file fileb://deps-layer.zip \
11 --compatible-runtimes nodejs18.x nodejs20.x
12
13# Attach the layer to a function
14aws lambda update-function-configuration \
15 --function-name MyFunction \
16 --layers arn:aws:lambda:us-east-1:123456789:layer:common-nodejs-deps:1AWS Lambda Powertools is a free, production-ready layer published by AWS that provides structured logging, X-Ray tracing, and custom metrics:
1from aws_lambda_powertools import Logger, Tracer, Metrics
2from aws_lambda_powertools.metrics import MetricUnit
3
4logger = Logger()
5tracer = Tracer()
6metrics = Metrics(namespace="MyApp")
7
8@logger.inject_lambda_context
9@tracer.capture_lambda_handler
10@metrics.log_metrics
11def handler(event, context):
12 metrics.add_metric(name="OrderProcessed", unit=MetricUnit.Count, value=1)
13 logger.info("Processing order", order_id=event["orderId"])
14 return {"statusCode": 200}Lambda Destinations
Destinations route the result of an asynchronous invocation to a target service after the function completes. They are strictly more powerful than DLQ.
Supported targets for both success and failure: SQS queue, SNS topic, EventBridge event bus, another Lambda function.
Destinations vs DLQ
| Feature | DLQ | Destinations |
|---|---|---|
| Applies to | Failures only | Success and failures |
| Payload | Original event only | Event + function response + metadata |
| Supported targets | SQS, SNS | SQS, SNS, EventBridge, Lambda |
| Recommendation | Legacy | Prefer Destinations |
1aws lambda put-function-event-invoke-config \
2 --function-name MyFunction \
3 --destination-config '{
4 "OnSuccess": { "Destination": "arn:aws:events:us-east-1:123:event-bus/my-bus" },
5 "OnFailure": { "Destination": "arn:aws:sqs:us-east-1:123:my-dlq" }
6 }'Versions and Aliases
Versions
A version is an immutable, point-in-time snapshot of your function's code AND configuration (memory, timeout, layers, environment variables). Once published, a version cannot be changed.
1# Publish a version from $LATEST
2aws lambda publish-version \
3 --function-name MyFunction \
4 --description "v2.3.1 — improved error handling"
5
6# Versioned ARN: arn:aws:lambda:us-east-1:123:function:MyFunction:7
7# $LATEST ARN: arn:aws:lambda:us-east-1:123:function:MyFunction:$LATESTAliases and Canary Deployments
An alias is a named, mutable pointer to a specific version. Aliases support weighted routing for safe canary deployments — critical for zero-downtime releases.
1# Create "prod" alias pointing to version 6
2aws lambda create-alias \
3 --function-name MyFunction --name prod --function-version 6
4
5# Canary: send 10% of traffic to v7, 90% stays on v6
6aws lambda update-alias \
7 --function-name MyFunction --name prod \
8 --function-version 6 \
9 --routing-config AdditionalVersionWeights={"7"=0.1}
10
11# After validation: promote v7 to 100% of traffic
12aws lambda update-alias \
13 --function-name MyFunction --name prod \
14 --function-version 7 \
15 --routing-config AdditionalVersionWeights={}CodeDeploy automates alias-based deployments with automatic rollback on CloudWatch Alarm breach:
Linear10PercentEvery1Minute— shift 10% per minute over 10 minutesCanary10Percent5Minutes— 10% first, wait 5 min, then 90%AllAtOnce— immediate full shift
Lambda Container Images
Package Lambda as a Docker container image (up to 10 GB) for large dependencies, custom runtimes, or reproducible builds.
1# Use the official AWS Lambda base image
2FROM public.ecr.aws/lambda/nodejs:20
3
4# Install production dependencies
5COPY package.json package-lock.json ${LAMBDA_TASK_ROOT}/
6RUN npm ci --omit=dev
7
8# Copy function code
9COPY index.mjs ${LAMBDA_TASK_ROOT}/
10
11# Set the handler (filename.exportedFunction)
12CMD ["index.handler"]1# Authenticate Docker to Amazon ECR
2aws ecr get-login-password --region us-east-1 \
3 | docker login --username AWS --password-stdin 123456789.dkr.ecr.us-east-1.amazonaws.com
4
5# Build, tag, and push the image
6docker build -t my-lambda:latest .
7docker tag my-lambda:latest 123456789.dkr.ecr.us-east-1.amazonaws.com/my-lambda:latest
8docker push 123456789.dkr.ecr.us-east-1.amazonaws.com/my-lambda:latestKey CloudWatch Metrics
| Metric | What it measures | Alert guidance |
|---|---|---|
Invocations | Total calls including retries | Traffic baseline |
Errors | Unhandled exceptions and timeouts | Alert if error rate > 1% |
Duration p99 | 99th-percentile execution time | Alert if approaching timeout |
Throttles | Invocations rejected by concurrency limit | Alert on any throttle in production |
ConcurrentExecutions | Simultaneous active environments | Alert when near reserved limit |
InitDuration | Cold start init time | Monitor after deployments |
REPORT RequestId: abc-123 Duration: 234.56 ms Billed: 235 ms
Memory Size: 512 MB Max Memory Used: 87 MB Init Duration: 318.45 ms
↑ Cold start indicator
The presence of Init Duration in the REPORT log line is the definitive cold start indicator.
DVA-C02 Quick Reference
| Topic | Key Fact |
|---|---|
| Max timeout | 15 minutes |
| Max memory | 10,240 MB |
| Default regional concurrency limit | 1,000 per region |
| API Gateway / ALB invocation | Synchronous (RequestResponse) |
| S3 / SNS / EventBridge invocation | Asynchronous (Event) |
| SQS / Kinesis / DynamoDB Streams | Poll-based (Event Source Mapping) |
| Eliminate cold starts | Provisioned Concurrency |
| Java cold start optimization | SnapStart |
| Reserved concurrency = 0 | Disables the function |
| Async retry count | 2 retries (3 total attempts) |
| Prefer over DLQ | Lambda Destinations |
| Versions are | Immutable |
| Aliases support | Weighted routing (canary deployments) |
| CPU scales with | Memory (1,769 MB = 1 full vCPU) |
| Max container image | 10 GB |
Practice Questions10
Q1. What is the maximum execution timeout for an AWS Lambda function?
Select one answer before revealing.
Q2. Which Lambda invocation type is used by Amazon API Gateway when calling a Lambda function?
Select one answer before revealing.
Q3. A Lambda function needs to connect to a private Amazon RDS instance inside a VPC. What is required?
Select one answer before revealing.
Q4. A Lambda function processes messages from an SQS queue. Occasionally, messages fail processing and reappear in the queue, causing infinite retries. Which TWO approaches resolve this? (Choose 2)
Select one answer before revealing.
Q5. A Lambda function written in Java is experiencing high cold start latency affecting user-facing P99 response times. Which TWO options best address this? (Choose 2)
Select one answer before revealing.
Q6. A developer wants to run different code for a Lambda function in development, staging, and production without changing the deployment package. What is the recommended approach?
Select one answer before revealing.
Q7. Which Lambda concurrency setting guarantees that a specific function will always have execution capacity available and prevents it from being throttled by other functions in the account?
Select one answer before revealing.
Q8. A developer needs to run code before a Lambda function is invoked to validate a JWT token, and cache the result for 5 minutes. What should they implement?
Select one answer before revealing.
Q9. A Lambda function processes asynchronous events from S3. For failed invocations, the developer wants to capture the original event, error details, and function response for debugging. What should they configure?
Select one answer before revealing.
Q10. A Lambda function is deployed in a VPC private subnet with no NAT Gateway. It needs to call the AWS DynamoDB API. What is the most cost-effective and secure solution?
Select one answer before revealing.