Lambda
05 / 08

Fundamentals & Execution Model

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 unzipped

Memory & 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 60

Lambda 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:3

Environment 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