AWS Lambda: Fundamentals & Execution Model
Lambda is AWS's serverless compute service. You provide code; AWS runs it in response to events, manages scaling, and charges only for compute time used. No servers to provision, no idle costs.
Function Anatomy
// Node.js handler signature
export const handler = async (event, context) => {
// event — input from the trigger (API GW request, S3 event, SQS message, etc.)
// context — runtime info (requestId, functionName, remainingTimeInMillis, etc.)
console.log('Event:', JSON.stringify(event, null, 2));
console.log('Remaining time:', context.getRemainingTimeInMillis(), 'ms');
// Return value: for sync invocations (API GW), this becomes the response
return {
statusCode: 200,
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ message: 'Hello!' }),
};
};# Python handler
import json
def handler(event, context):
print(f"Request ID: {context.aws_request_id}")
print(f"Remaining time: {context.get_remaining_time_in_millis()}ms")
return {
'statusCode': 200,
'body': json.dumps({'message': 'Hello!'})
}Supported Runtimes
Node.js 22.x, 20.x, 18.x (most popular for web APIs)
Python 3.13, 3.12, 3.11, 3.10 (popular for data/ML/scripting)
Java 21, 17, 11, 8 (SnapStart for cold start reduction)
Go 1.x (custom runtime, fastest cold starts)
Ruby 3.3, 3.2
.NET 8 (C#)
Custom Runtime: any language via bootstrap binary (Rust, Bash, etc.)
Container image: up to 10GB, any language/runtime, ECR-hosted
Execution Environment Lifecycle
Lambda reuses execution environments for subsequent invocations. Code at module level runs once during the INIT phase; handler runs per invocation. Use this to cache DB connections, SDK clients, etc.
// INIT phase — runs once per cold start
// Initialize expensive resources OUTSIDE the handler
import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
import { DynamoDBDocumentClient } from '@aws-sdk/lib-dynamodb';
// DB client created once, reused across warm invocations
const ddbClient = new DynamoDBClient({});
const db = DynamoDBDocumentClient.from(ddbClient);
// INVOKE phase — runs every time
export const handler = async (event) => {
// db is already initialized — no cold start penalty here
const result = await db.get({ TableName: 'users', Key: { id: event.userId } });
return result.Item;
};Configuration Limits
Setting | Default | Max
---------------------|----------|------------------
Memory | 128 MB | 10,240 MB (10 GB)
Timeout | 3 sec | 900 sec (15 min)
/tmp storage | 512 MB | 10,240 MB
Env variables | — | 4 KB total
Payload (sync) | — | 6 MB request/response
Payload (async) | — | 256 KB
Container image size | — | 10 GB (uncompressed)
Deployment pkg size | — | 50 MB zipped, 250 MB unzippedMemory & CPU Relationship
Lambda does not let you set CPU directly. More memory = more vCPU proportionally. At 1769 MB you get 1 full vCPU; 3538 MB gives 2 vCPUs. For CPU-bound tasks, increasing memory reduces duration, often making the total cost neutral or cheaper.
# Create function
aws lambda create-function \
--function-name my-api-handler \
--runtime nodejs20.x \
--handler index.handler \
--role arn:aws:iam::123456789012:role/lambda-exec-role \
--zip-file fileb://function.zip \
--memory-size 512 \
--timeout 30 \
--environment Variables='{DB_URL=postgresql://...,LOG_LEVEL=info}'
# Update config
aws lambda update-function-configuration \
--function-name my-api-handler \
--memory-size 1024 \
--timeout 60Lambda Layers
Layers are ZIP archives with libraries, custom runtimes, or configuration. Each function can use up to 5 layers. Shared across functions, versioned, useful for large dependencies (e.g., numpy/pandas for Python).
# Create layer
zip -r layer.zip nodejs/ # Must follow structure: nodejs/node_modules/...
aws lambda publish-layer-version \
--layer-name my-shared-deps \
--zip-file fileb://layer.zip \
--compatible-runtimes nodejs20.x
# Attach layer to function
aws lambda update-function-configuration \
--function-name my-api-handler \
--layers arn:aws:lambda:eu-west-1:123:layer:my-shared-deps:3Environment Variables & Secrets
# Env vars encrypted at rest with KMS (Lambda service key by default)
# For sensitive values: use Secrets Manager or SSM Parameter Store at runtime
aws lambda update-function-configuration \
--function-name my-api-handler \
--environment Variables='{
"NODE_ENV": "production",
"LOG_LEVEL": "warn"
}'// Fetch secret at cold start (cache for warm invocations)
import { SecretsManagerClient, GetSecretValueCommand } from '@aws-sdk/client-secrets-manager';
const sm = new SecretsManagerClient({});
let dbPassword; // cached across warm invocations
async function getDbPassword() {
if (dbPassword) return dbPassword;
const res = await sm.send(new GetSecretValueCommand({ SecretId: 'prod/db/password' }));
dbPassword = JSON.parse(res.SecretString).password;
return dbPassword;
}Keep your own version of these notes — editable, searchable, and organised by your stack.
Start free