AWS Lambda — Serverless Functions Complete Guide

Sanjeev SharmaSanjeev Sharma
5 min read

Advertisement

Introduction

Why This Matters

AWS Lambda executes code in response to events without provisioning or managing servers. You pay only for compute time consumed — billed in 1ms increments. Lambda integrates natively with over 200 AWS services, making it the foundation for event-driven architectures: API backends, image processing pipelines, scheduled jobs, stream processors, and more. Understanding Lambda limits, cold starts, and deployment patterns is essential for building cost-effective serverless applications.

Creating Lambda Functions

# handler.py — Python Lambda handler
import json
import boto3
import os
 
def lambda_handler(event, context):
    """
    event: trigger-specific data (API Gateway request, S3 event, etc.)
    context: Lambda runtime info (function name, timeout, request ID)
    """
    print(f"Event: {json.dumps(event)}")
    print(f"Remaining time: {context.get_remaining_time_in_millis()}ms")
 
    # Access environment variables
    table_name = os.environ['DYNAMODB_TABLE']
 
    # Call AWS service
    dynamodb = boto3.resource('dynamodb')
    table = dynamodb.Table(table_name)
 
    return {
        'statusCode': 200,
        'headers': {
            'Content-Type': 'application/json',
            'Access-Control-Allow-Origin': '*'
        },
        'body': json.dumps({'message': 'Success'})
    }
# Package and deploy
zip -r function.zip handler.py
 
aws lambda create-function \
  --function-name my-api \
  --runtime python3.12 \
  --role arn:aws:iam::123456789012:role/lambda-execution-role \
  --handler handler.lambda_handler \
  --zip-file fileb://function.zip \
  --timeout 30 \
  --memory-size 256 \
  --environment Variables='{DYNAMODB_TABLE=my-table}'
 
# Update function code
aws lambda update-function-code \
  --function-name my-api \
  --zip-file fileb://function.zip
 
# Invoke manually for testing
aws lambda invoke \
  --function-name my-api \
  --payload '{"key": "value"}' \
  --cli-binary-format raw-in-base64-out \
  response.json
cat response.json

Node.js Lambda

// handler.js
const { DynamoDBClient, GetItemCommand } = require('@aws-sdk/client-dynamodb');
 
const client = new DynamoDBClient({ region: process.env.AWS_REGION });
 
exports.handler = async (event, context) => {
  const { pathParameters } = event;
  const userId = pathParameters?.id;
 
  try {
    const response = await client.send(new GetItemCommand({
      TableName: process.env.DYNAMODB_TABLE,
      Key: { userId: { S: userId } }
    }));
 
    if (!response.Item) {
      return { statusCode: 404, body: JSON.stringify({ error: 'Not found' }) };
    }
 
    return {
      statusCode: 200,
      body: JSON.stringify(response.Item)
    };
  } catch (error) {
    console.error('Error:', error);
    return { statusCode: 500, body: JSON.stringify({ error: 'Internal error' }) };
  }
};

Triggers and Event Sources

# API Gateway trigger — HTTP endpoint
aws apigatewayv2 create-api \
  --name my-api \
  --protocol-type HTTP
 
aws lambda add-permission \
  --function-name my-api \
  --statement-id api-gateway-invoke \
  --action lambda:InvokeFunction \
  --principal apigateway.amazonaws.com
 
# S3 trigger — on file upload
aws s3api put-bucket-notification-configuration \
  --bucket my-bucket \
  --notification-configuration '{
    "LambdaFunctionConfigurations": [{
      "LambdaFunctionArn": "arn:aws:lambda:us-east-1:123456789012:function:process-upload",
      "Events": ["s3:ObjectCreated:*"],
      "Filter": {"Key": {"FilterRules": [{"Name": "suffix", "Value": ".jpg"}]}}
    }]
  }'
 
# SQS trigger — process queue messages
aws lambda create-event-source-mapping \
  --function-name process-orders \
  --event-source-arn arn:aws:sqs:us-east-1:123456789012:orders-queue \
  --batch-size 10 \
  --maximum-batching-window-in-seconds 5
 
# EventBridge (CloudWatch Events) — scheduled job
aws events put-rule \
  --name daily-cleanup \
  --schedule-expression "cron(0 2 * * ? *)"

Layers and Dependencies

# Create a Lambda Layer for shared dependencies
mkdir -p layer/python
pip install requests boto3 -t layer/python/
cd layer && zip -r ../my-layer.zip . && cd ..
 
aws lambda publish-layer-version \
  --layer-name my-dependencies \
  --zip-file fileb://my-layer.zip \
  --compatible-runtimes python3.11 python3.12
 
# Attach layer to function
aws lambda update-function-configuration \
  --function-name my-api \
  --layers arn:aws:lambda:us-east-1:123456789012:layer:my-dependencies:1

Concurrency and Cold Starts

# Set reserved concurrency (limits max concurrent executions)
aws lambda put-function-concurrency \
  --function-name my-api \
  --reserved-concurrent-executions 100
 
# Provisioned concurrency — eliminates cold starts
aws lambda put-provisioned-concurrency-config \
  --function-name my-api \
  --qualifier production \
  --provisioned-concurrent-executions 10
 
# Check concurrency utilization
aws cloudwatch get-metric-statistics \
  --namespace AWS/Lambda \
  --metric-name ConcurrentExecutions \
  --dimensions Name=FunctionName,Value=my-api \
  --statistics Maximum \
  --period 60 \
  --start-time 2024-01-01T00:00:00Z \
  --end-time 2024-01-01T01:00:00Z

Common Mistakes

  • Setting memory too low — Lambda CPU scales proportionally with memory; 512MB often runs faster and cheaper than 128MB
  • Not handling errors and retries properly — async Lambda invocations retry twice by default; write idempotent handlers
  • Putting Lambda in a VPC without understanding the latency and cold start implications
  • Bundling large dependencies in deployment packages — use Layers for shared libraries or container images for large runtimes
  • Not setting Dead Letter Queues (DLQ) for async invocations — failed events disappear silently without DLQ

Best Practices

  • Keep handlers lean — initialize AWS SDK clients outside the handler function to reuse across warm invocations
  • Use environment variables for configuration; use AWS Secrets Manager for sensitive values
  • Set function timeouts conservatively — default 3 seconds is too short for most real workloads
  • Enable Lambda Insights (CloudWatch) for detailed performance metrics and traces
  • Use Lambda Power Tuning (open source) to find the optimal memory setting for cost and performance
  • Deploy with infrastructure as code (Terraform, SAM, CDK) — never manual console deployments

Key Takeaways

  • Lambda runs code in response to events — you pay only for compute time in 1ms increments with no idle costs
  • Cold starts occur when Lambda initializes a new execution environment — provisioned concurrency eliminates cold starts at added cost
  • Memory setting directly controls CPU allocation — higher memory often reduces duration enough to lower total cost
  • Layers allow sharing dependencies across multiple Lambda functions without duplicating deployment packages
  • Reserved concurrency limits maximum concurrent executions — protects downstream services from overload
  • Lambda integrates natively with API Gateway, S3, SQS, SNS, DynamoDB Streams, EventBridge, and Kinesis
  • Container image support allows packaging up to 10GB — suitable for ML inference and large runtimes
  • Initialize external connections (database, SDK clients) outside the handler to reuse across warm Lambda invocations

Advertisement

Sanjeev Sharma

Written by

Sanjeev Sharma

Full Stack Engineer · E-mopro

Related reading