Writing Lambda functions is easy. Running them efficiently in production requires attention to performance, cost, and reliability.

Minimize Cold Starts

A cold start occurs when Lambda initializes a new execution environment. It adds latency (100ms–3s+).

Keep Packages Small

  # BAD — entire AWS SDK (already in Lambda runtime)
pip install boto3 -t package/

# GOOD — only what you need
pip install requests -t package/
# boto3 is pre-installed in Lambda
  

Remove unnecessary files from deployment packages:

  find package/ -name "*.pyc" -delete
find package/ -name "tests" -type d -exec rm -rf {} +
find package/ -name "__pycache__" -type d -exec rm -rf {} +
  

Use Lambda Layers

Share heavy dependencies across functions:

  pip install numpy pandas -t python/lib/python3.12/site-packages/
zip -r data-layer.zip python/
aws lambda publish-layer-version --layer-name data-science --zip-file fileb://data-layer.zip
  

Provisioned Concurrency

Eliminate cold starts for latency-sensitive endpoints:

  aws lambda put-provisioned-concurrency-config \
    --function-name my-api \
    --provisioned-concurrent-executions 5
  

Costs more but guarantees warm instances.

Memory and CPU Tuning

Memory affects CPU proportionally. More memory = faster execution (sometimes cheaper overall):

  # Test different memory settings
aws lambda update-function-configuration --function-name my-func --memory-size 512
aws lambda update-function-configuration --function-name my-func --memory-size 1024
  

Use AWS Lambda Power Tuning tool to find the optimal memory/cost ratio.

Workload Recommended Memory
Simple API handler 128–256 MB
Data processing 512–1024 MB
ML inference 1024–3008 MB
Image processing 512–1024 MB

Initialize Outside the Handler

Reuse connections across invocations:

  import boto3

# Created once — reused across warm invocations
dynamodb = boto3.resource("dynamodb")
table = dynamodb.Table("MyTable")
http_client = boto3.client("secretsmanager")

def lambda_handler(event, context):
    # Fast — connection already established
    result = table.get_item(Key={"id": event["id"]})
    return result
  
  # BAD — creates new connection every invocation
def lambda_handler(event, context):
    dynamodb = boto3.resource("dynamodb")
    table = dynamodb.Table("MyTable")
    ...
  

Set Appropriate Timeouts

  # Default is 3 seconds — often too short
# API handlers: 10–30 seconds
# Data processing: 60–300 seconds
# Max: 900 seconds (15 minutes)
  

Error Handling and Retries

  import json
import logging

logger = logging.getLogger()
logger.setLevel(logging.INFO)

def lambda_handler(event, context):
    try:
        result = process(event)
        return {"statusCode": 200, "body": json.dumps(result)}
    except ValueError as e:
        logger.warning("Validation error: %s", e)
        return {"statusCode": 400, "body": json.dumps({"error": str(e)})}
    except Exception:
        logger.exception("Unhandled error")
        raise  # Lambda retries on unhandled exceptions
  

Configure Dead Letter Queue (DLQ) for failed invocations:

  # SAM template
DeadLetterQueue:
  Type: SQS
  TargetArn: !GetAtt DLQQueue.Arn
  

Environment Variables

  import os

DEBUG = os.environ.get("DEBUG", "false") == "true"
TABLE_NAME = os.environ["TABLE_NAME"]  # required — fail fast if missing
  

Use AWS Secrets Manager or Parameter Store for sensitive values — not environment variables in production.

Monitoring

Structured Logging

  import json
import logging

logger = logging.getLogger()

def lambda_handler(event, context):
    logger.info(json.dumps({
        "request_id": context.aws_request_id,
        "function": context.function_name,
        "memory_limit": context.memory_limit_in_mb,
        "event_type": event.get("httpMethod", "direct"),
    }))
  

CloudWatch Metrics

Key metrics to watch:

  • Duration — execution time
  • Errors — failed invocations
  • Throttles — concurrency limit hit
  • ConcurrentExecutions — current load
  • IteratorAge — stream processing lag

X-Ray Tracing

  from aws_xray_sdk.core import xray_recorder

@xray_recorder.capture("process_order")
def process_order(order_id):
    ...
  

Enable active tracing in Lambda configuration.

Cost Optimization

  1. Right-size memory — don’t over-provision
  2. Reduce invocations — batch events, use SQS buffering
  3. Avoid provisioned concurrency unless latency requires it
  4. Use Graviton (ARM) — 20% cheaper: --architectures arm64
  5. Set reserved concurrency to prevent runaway costs

Security Checklist

  • Least-privilege IAM role
  • Secrets in Secrets Manager, not env vars
  • VPC only when accessing private resources
  • Enable AWS CloudTrail logging
  • Validate all input in the handler
  • Keep runtime updated (Python 3.12+)

These practices keep Lambda functions fast, reliable, and cost-effective at scale.