Serverless & Lambda

Lambda at scale, cold starts, concurrency, and serverless patterns beyond hello world

Lesson 8 of 27 available15 practice questions

ACTIVE PRACTICE Β· 15 practice questions

Make this lesson stick.

Test what you can recall and learn from the feedback. Come back to the lesson whenever you need an explanation.

Sign in to practice β†’

Last generated

Serverless & Lambda

Master serverless computing with AWS Lambda using free flashcards and spaced repetition practice. This lesson covers Lambda functions, event-driven architecture, and scaling patternsβ€”essential concepts for building modern cloud applications without managing servers.

Welcome to Serverless Computing πŸš€

Imagine running your code without ever provisioning, configuring, or managing a single server. That's the promise of serverless computing. AWS Lambda revolutionized cloud architecture by letting you execute code in response to eventsβ€”and you only pay for the compute time you consume, measured in milliseconds.

In this lesson, you'll learn how to leverage Lambda for real-world applications, understand execution models, and avoid common pitfalls that catch even experienced developers.

Core Concepts

What is Serverless? πŸ’‘

Serverless doesn't mean "no servers"β€”it means you don't manage them. AWS handles provisioning, scaling, patching, and availability. You focus entirely on your code.

Key characteristics:

  • Event-driven execution: Code runs in response to triggers (HTTP requests, file uploads, database changes)
  • Automatic scaling: From zero to thousands of concurrent executions
  • Pay-per-use: Charged only for actual execution time (billed in 1ms increments)
  • Stateless: Each invocation is independent; state must be stored externally
Traditional vs Serverless Architecture

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚           TRADITIONAL SERVER                     β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚  πŸ–₯️ Server running 24/7                         β”‚
β”‚  πŸ’° Pay for idle time                           β”‚
β”‚  πŸ“Š Manual scaling                              β”‚
β”‚  πŸ”§ OS patching & management                    β”‚
β”‚  ⚠️ Over/under provisioning risk                β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚           SERVERLESS (LAMBDA)                    β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚  ⚑ Runs only when triggered                     β”‚
β”‚  πŸ’° Pay only for execution time                 β”‚
β”‚  πŸ“Š Auto-scales to demand                       β”‚
β”‚  πŸ”§ Zero infrastructure management              β”‚
β”‚  βœ… Perfect capacity every time                 β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

AWS Lambda Fundamentals πŸ”Ί

A Lambda function is a stateless compute service that executes your code in response to events.

Core components:

ComponentDescriptionExample
HandlerEntry point function AWS invokeslambda_handler(event, context)
EventJSON input containing trigger dataS3 object details, API request body
ContextRuntime information (request ID, timeout, memory)context.get_remaining_time_in_millis()
RuntimeExecution environment (language version)Python 3.11, Node.js 18.x, Java 17
Execution RoleIAM permissions for AWS service accessRead S3, write DynamoDB

Basic Python Lambda function:

import json

def lambda_handler(event, context):
    # Extract data from event
    name = event.get('name', 'World')
    
    # Business logic
    message = f'Hello, {name}!'
    
    # Return response
    return {
        'statusCode': 200,
        'body': json.dumps({'message': message})
    }

Node.js Lambda function:

exports.handler = async (event) => {
    const name = event.name || 'World';
    
    const response = {
        statusCode: 200,
        body: JSON.stringify({
            message: `Hello, ${name}!`
        })
    };
    
    return response;
};

πŸ’‘ Tip: Lambda supports multiple languages: Python, Node.js, Java, Go, Ruby, .NET, and custom runtimes.

Lambda Execution Model ⚑

Understanding the lifecycle of a Lambda invocation is crucial for optimization:

LAMBDA INVOCATION LIFECYCLE

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ COLD START (first invocation or after idle)   β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚                                                β”‚
β”‚  1️⃣ Download code package                      β”‚
β”‚     β”‚ (from S3)                               β”‚
β”‚     ↓                                          β”‚
β”‚  2️⃣ Start execution environment                β”‚
β”‚     β”‚ (container, runtime)                    β”‚
β”‚     ↓                                          β”‚
β”‚  3️⃣ Initialize (run code outside handler)      β”‚
β”‚     β”‚ (imports, connections, config)          β”‚
β”‚     ↓                                          β”‚
β”‚  4️⃣ Invoke handler function                    β”‚
β”‚     β”‚ (your business logic)                   β”‚
β”‚     ↓                                          β”‚
β”‚  5️⃣ Return response                            β”‚
β”‚                                                β”‚
β”‚  ⏱️ Total: 100-1000ms depending on setup       β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ WARM START (reused environment)               β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚                                                β”‚
β”‚  βœ… Environment already initialized             β”‚
β”‚     β”‚                                          β”‚
β”‚     ↓                                          β”‚
β”‚  4️⃣ Invoke handler function                    β”‚
β”‚     β”‚ (your business logic)                   β”‚
β”‚     ↓                                          β”‚
β”‚  5️⃣ Return response                            β”‚
β”‚                                                β”‚
β”‚  ⏱️ Total: 1-10ms (much faster!)               β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Cold start optimization strategies:

  1. Keep functions small: Smaller deployment packages download faster
  2. Minimize initialization: Move one-time setup outside handler but keep it lightweight
  3. Use Provisioned Concurrency: Pre-warm execution environments for consistent latency
  4. Choose compiled languages: Java/C# have longer cold starts than Python/Node.js

Example: Optimized initialization

import boto3
import os

## Initialize OUTSIDE handler (runs once per container)
dynamodb = boto3.resource('dynamodb')
table = dynamodb.Table(os.environ['TABLE_NAME'])

def lambda_handler(event, context):
    # Handler runs on every invocation (use pre-initialized resources)
    user_id = event['userId']
    
    response = table.get_item(Key={'userId': user_id})
    
    return {
        'statusCode': 200,
        'body': json.dumps(response.get('Item', {}))
    }

⚠️ Common mistake: Initializing resources inside the handler causes redundant work on every invocation.

Event Sources & Triggers 🎯

Lambda integrates with 50+ AWS services as event sources:

Event SourceUse CaseInvocation Type
API GatewayREST/HTTP APIs, webhooksSynchronous
S3Process uploaded files (images, logs)Asynchronous
DynamoDB StreamsReact to database changesStream (poll-based)
SQSQueue processing, background jobsStream (poll-based)
EventBridgeScheduled tasks, custom eventsAsynchronous
KinesisReal-time data streamingStream (poll-based)
SNSPub/sub notificationsAsynchronous
ALBApplication Load Balancer targetsSynchronous

Invocation types:

  • Synchronous: Caller waits for response (API Gateway, ALB)
  • Asynchronous: Lambda queues event, returns immediately (S3, SNS)
  • Stream-based: Lambda polls source and processes batches (SQS, Kinesis, DynamoDB Streams)
EVENT-DRIVEN ARCHITECTURE FLOW

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚   User      β”‚
β”‚  (Mobile)   β”‚
β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜
       β”‚ 1. Upload photo
       ↓
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”        2. S3 Event         β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚     S3      │─────────────────────────────→│   Lambda     β”‚
β”‚   Bucket    β”‚                              β”‚  (Resize)    β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                              β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜
                                                    β”‚ 3. Store metadata
                                                    ↓
                                             β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                                             β”‚  DynamoDB    β”‚
                                             β”‚    Table     β”‚
                                             β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜
                                                    β”‚ 4. Stream change
                                                    ↓
                                             β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                                             β”‚   Lambda     β”‚
                                             β”‚  (Notify)    β”‚
                                             β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜
                                                    β”‚ 5. Send notification
                                                    ↓
                                             β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                                             β”‚     SNS      β”‚
                                             β”‚   Topic      β”‚
                                             β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Memory, Timeout & Concurrency Configuration πŸ“Š

Memory allocation (128 MB to 10,240 MB):

  • Determines CPU power proportionally
  • More memory = faster execution = potentially lower cost
  • Testing optimal memory is crucial for cost/performance
## Example: Memory impacts execution time
## 512 MB: 5 seconds = $0.000000833 Γ— 5000ms = $0.00417
## 1024 MB: 2.5 seconds = $0.000001667 Γ— 2500ms = $0.00417
## Same cost, but 2Γ— faster response! ⚑

Timeout (1 second to 15 minutes maximum):

  • Default: 3 seconds
  • Set based on expected execution time
  • Function terminates if timeout exceeded

⚠️ Critical: API Gateway has 29-second timeoutβ€”Lambda timeout must be less!

Concurrency:

  • Account limit: 1,000 concurrent executions per region (default, can increase)
  • Reserved concurrency: Guarantee capacity for critical functions
  • Provisioned concurrency: Keep functions warm (eliminates cold starts)
CONCURRENCY MODEL

Scenario: 1,000 requests/second, 100ms execution time

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Required Concurrency = RPS Γ— Duration         β”‚
β”‚  = 1,000 req/s Γ— 0.1s = 100 concurrent        β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

  Request Flow:
  
  T=0s    100 invocations start
  T=0.1s  100 complete, next 100 start
  T=0.2s  100 complete, next 100 start
  ...
  
  Steady state: 100 concurrent executions

Practical Examples

Example 1: API Backend with API Gateway 🌐

Building a serverless REST API for a task management application.

Architecture:

Client β†’ API Gateway β†’ Lambda β†’ DynamoDB

Lambda function (Python):

import json
import boto3
import uuid
from datetime import datetime

dynamodb = boto3.resource('dynamodb')
table = dynamodb.Table('Tasks')

def lambda_handler(event, context):
    http_method = event['httpMethod']
    
    if http_method == 'GET':
        # List all tasks
        response = table.scan()
        return {
            'statusCode': 200,
            'headers': {'Content-Type': 'application/json'},
            'body': json.dumps(response['Items'])
        }
    
    elif http_method == 'POST':
        # Create new task
        body = json.loads(event['body'])
        
        task = {
            'taskId': str(uuid.uuid4()),
            'title': body['title'],
            'status': 'pending',
            'createdAt': datetime.utcnow().isoformat()
        }
        
        table.put_item(Item=task)
        
        return {
            'statusCode': 201,
            'headers': {'Content-Type': 'application/json'},
            'body': json.dumps(task)
        }
    
    elif http_method == 'PUT':
        # Update task status
        body = json.loads(event['body'])
        task_id = event['pathParameters']['taskId']
        
        table.update_item(
            Key={'taskId': task_id},
            UpdateExpression='SET #status = :status',
            ExpressionAttributeNames={'#status': 'status'},
            ExpressionAttributeValues={':status': body['status']}
        )
        
        return {
            'statusCode': 200,
            'body': json.dumps({'message': 'Task updated'})
        }
    
    else:
        return {
            'statusCode': 405,
            'body': json.dumps({'error': 'Method not allowed'})
        }

Key patterns:

  • Single Lambda handling multiple HTTP methods (monolithic approach)
  • Alternative: One Lambda per endpoint (microservices approach)
  • DynamoDB for sub-10ms latency
  • Proper HTTP status codes and headers

πŸ’‘ Production tip: Use API Gateway request validation to reject invalid requests before Lambda invocation (saves cost).

Example 2: S3 Image Processing Pipeline πŸ“Έ

Automatic thumbnail generation when images are uploaded.

Workflow:

User uploads image.jpg β†’ S3 Bucket
                           ↓ (S3 Event)
                       Lambda Function
                           ↓
                    Resize to 200x200
                           ↓
                  Save to S3 /thumbnails/

Lambda function (Python with Pillow):

import boto3
import os
from PIL import Image
from io import BytesIO

s3 = boto3.client('s3')

def lambda_handler(event, context):
    # Extract S3 event information
    bucket = event['Records'][0]['s3']['bucket']['name']
    key = event['Records'][0]['s3']['object']['key']
    
    # Skip if already a thumbnail
    if key.startswith('thumbnails/'):
        return {'statusCode': 200, 'body': 'Already a thumbnail'}
    
    # Download original image
    response = s3.get_object(Bucket=bucket, Key=key)
    image_content = response['Body'].read()
    
    # Resize image
    image = Image.open(BytesIO(image_content))
    image.thumbnail((200, 200), Image.LANCZOS)
    
    # Save to buffer
    buffer = BytesIO()
    image.save(buffer, format=image.format)
    buffer.seek(0)
    
    # Upload thumbnail
    thumbnail_key = f'thumbnails/{key}'
    s3.put_object(
        Bucket=bucket,
        Key=thumbnail_key,
        Body=buffer,
        ContentType=response['ContentType']
    )
    
    return {
        'statusCode': 200,
        'body': f'Thumbnail created: {thumbnail_key}'
    }

Deployment considerations:

  • Layer usage: Package Pillow as a Lambda Layer (reduces deployment size)
  • Memory: Image processing needs 512-1024 MB for decent performance
  • Timeout: Set 1-2 minutes for large images
  • Error handling: Handle corrupted images gracefully

⚠️ Watch out: S3 events are asynchronousβ€”Lambda retries failures automatically. Make operations idempotent!

Example 3: Scheduled Data Processing with EventBridge ⏰

Daily report generation running at 6 AM UTC.

EventBridge Rule:

{
  "schedule": "cron(0 6 * * ? *)",
  "target": "arn:aws:lambda:us-east-1:123456789012:function:DailyReport"
}

Lambda function (Node.js):

const AWS = require('aws-sdk');
const dynamodb = new AWS.DynamoDB.DocumentClient();
const ses = new AWS.SES();

exports.handler = async (event) => {
    try {
        // Query yesterday's orders
        const yesterday = new Date();
        yesterday.setDate(yesterday.getDate() - 1);
        const dateKey = yesterday.toISOString().split('T')[0];
        
        const params = {
            TableName: 'Orders',
            IndexName: 'DateIndex',
            KeyConditionExpression: 'orderDate = :date',
            ExpressionAttributeValues: {
                ':date': dateKey
            }
        };
        
        const result = await dynamodb.query(params).promise();
        
        // Calculate metrics
        const totalOrders = result.Items.length;
        const totalRevenue = result.Items.reduce(
            (sum, item) => sum + item.amount, 0
        );
        
        // Generate report email
        const emailParams = {
            Source: 'reports@example.com',
            Destination: {
                ToAddresses: ['management@example.com']
            },
            Message: {
                Subject: {
                    Data: `Daily Report - ${dateKey}`
                },
                Body: {
                    Text: {
                        Data: `Total Orders: ${totalOrders}\nRevenue: $${totalRevenue.toFixed(2)}`
                    }
                }
            }
        };
        
        await ses.sendEmail(emailParams).promise();
        
        return {
            statusCode: 200,
            body: JSON.stringify({
                message: 'Report sent successfully',
                metrics: { totalOrders, totalRevenue }
            })
        };
        
    } catch (error) {
        console.error('Report generation failed:', error);
        throw error;
    }
};

Cron expression breakdown:

cron(0 6 * * ? *)
     β”‚ β”‚ β”‚ β”‚ β”‚ β”‚
     β”‚ β”‚ β”‚ β”‚ β”‚ └─ Year (not required)
     β”‚ β”‚ β”‚ β”‚ └─── Day of week (? = any)
     β”‚ β”‚ β”‚ └───── Month (1-12)
     β”‚ β”‚ └─────── Day of month (1-31)
     β”‚ └───────── Hour (0-23) - 6 AM
     └─────────── Minute (0-59) - 0 minutes

πŸ’‘ Pro tip: Use CloudWatch Logs Insights to analyze execution patterns and identify optimization opportunities.

Example 4: DynamoDB Stream Processing πŸ“Š

Real-time analytics on database changes.

Use case: Track user activity score as items are added/updated/deleted.

import boto3
from decimal import Decimal

dynamodb = boto3.resource('dynamodb')
scores_table = dynamodb.Table('UserScores')

def lambda_handler(event, context):
    for record in event['Records']:
        event_name = record['eventName']  # INSERT, MODIFY, REMOVE
        
        if event_name in ['INSERT', 'MODIFY']:
            # Get new image
            new_item = record['dynamodb']['NewImage']
            user_id = new_item['userId']['S']
            action = new_item['action']['S']
            
            # Calculate points
            points = {
                'post_created': 10,
                'comment_added': 5,
                'like_given': 1
            }.get(action, 0)
            
            # Update user score atomically
            scores_table.update_item(
                Key={'userId': user_id},
                UpdateExpression='ADD score :points',
                ExpressionAttributeValues={':points': Decimal(points)}
            )
            
        elif event_name == 'REMOVE':
            # Handle deletions if needed
            pass
    
    return {
        'statusCode': 200,
        'body': f'Processed {len(event["Records"])} records'
    }

Stream processing characteristics:

  • Batch processing: Lambda receives multiple records (1-10,000)
  • At-least-once delivery: Design for idempotency
  • Ordered processing: Within a shard, records are ordered
  • Automatic retries: Failed batches are retried

⚠️ Critical pattern: Use ADD operation in DynamoDB for atomic counters (handles duplicates gracefully).

Common Mistakes ⚠️

1. Ignoring Cold Starts in Latency-Sensitive Apps

Problem: API endpoints with 2-second cold starts frustrate users.

Solution:

  • Use Provisioned Concurrency for critical endpoints
  • Keep deployment packages under 50 MB
  • Choose interpreted languages (Python, Node.js) for faster starts
  • Consider Lambda@Edge for global distribution

2. Not Setting Appropriate Timeouts

Problem: Default 3-second timeout causes premature function termination.

Example:

## ❌ BAD: No timeout consideration
def lambda_handler(event, context):
    # This might take 5 seconds!
    result = heavy_computation()
    return result  # Function terminated at 3 seconds
## βœ… GOOD: Check remaining time
import time

def lambda_handler(event, context):
    remaining = context.get_remaining_time_in_millis()
    
    if remaining < 5000:  # Need at least 5 seconds
        return {'error': 'Insufficient time remaining'}
    
    result = heavy_computation()
    return result

Fix: Set timeout to 2Γ— expected execution time with monitoring.

3. Creating Resources Inside Handler

Problem: Initializing AWS clients on every invocation wastes time and money.

## ❌ BAD: Initialize every time
def lambda_handler(event, context):
    s3 = boto3.client('s3')  # Repeated work!
    response = s3.get_object(Bucket='my-bucket', Key='file.txt')
    return response
## βœ… GOOD: Initialize once
import boto3

s3 = boto3.client('s3')  # Reused across invocations

def lambda_handler(event, context):
    response = s3.get_object(Bucket='my-bucket', Key='file.txt')
    return response

4. Insufficient IAM Permissions (or Too Many)

Problem: Following the principle of least privilege is often overlooked.

// ❌ BAD: Overly permissive
{
  "Effect": "Allow",
  "Action": "s3:*",
  "Resource": "*"
}
// βœ… GOOD: Specific permissions
{
  "Effect": "Allow",
  "Action": ["s3:GetObject", "s3:PutObject"],
  "Resource": "arn:aws:s3:::my-specific-bucket/*"
}

5. Not Handling Async Errors Properly

Problem: S3/SNS/EventBridge invocations retry automaticallyβ€”failures can cause infinite loops.

Solution:

  • Configure Dead Letter Queue (DLQ) for failed events
  • Set maximum retry attempts (default is 2)
  • Implement idempotency checks
## βœ… Idempotent S3 processing
import hashlib

processed_cache = {}  # In production, use DynamoDB

def lambda_handler(event, context):
    key = event['Records'][0]['s3']['object']['key']
    etag = event['Records'][0]['s3']['object']['eTag']
    
    # Create unique identifier
    cache_key = hashlib.md5(f'{key}:{etag}'.encode()).hexdigest()
    
    if cache_key in processed_cache:
        return {'statusCode': 200, 'body': 'Already processed'}
    
    # Process file
    process_file(key)
    
    processed_cache[cache_key] = True
    return {'statusCode': 200}

6. Deploying Large Dependencies

Problem: 250 MB deployment limit includes dependencies.

Solution:

  • Use Lambda Layers for shared libraries
  • Remove unused packages
  • Use lightweight alternatives (e.g., boto3 already included in Python runtime)
## Check package size before deployment
du -sh ./deployment-package
## If > 50MB, consider layers or trimming

7. Not Monitoring Costs

Problem: Inefficient functions with high memory/long duration cost more than expected.

Monitoring:

  • AWS Cost Explorer: Track Lambda spending
  • CloudWatch Logs Insights: Analyze duration patterns
  • Lambda Power Tuning: Find optimal memory configuration
## Power Tuning finds sweet spot
128 MB: 5000ms execution = $0.00042
512 MB: 1500ms execution = $0.00025  ← Optimal!
1024 MB: 1000ms execution = $0.00033

πŸ’‘ Did you know? AWS Lambda Power Tuning is an open-source tool that automatically tests different memory configurations to find the most cost-effective setting for your function.

Key Takeaways 🎯

βœ… Serverless means no infrastructure managementβ€”focus on code, AWS handles everything else

βœ… Cold starts matterβ€”optimize initialization, use Provisioned Concurrency for critical paths

βœ… Event-driven architectureβ€”Lambda excels at responding to S3, DynamoDB, API Gateway, and 50+ other triggers

βœ… Pay only for execution timeβ€”measured in milliseconds, making it cost-effective for sporadic workloads

βœ… Memory = CPUβ€”more memory means proportionally more CPU; test to find optimal cost/performance

βœ… Stateless by designβ€”use S3, DynamoDB, ElastiCache for state persistence

βœ… Concurrent execution scales automaticallyβ€”from 0 to 1,000s, but understand account limits

βœ… Idempotency is criticalβ€”especially for async/retry scenarios (S3, DynamoDB Streams)

βœ… Monitoring and loggingβ€”CloudWatch Logs/Metrics are essential for debugging and optimization

βœ… Security firstβ€”least-privilege IAM roles, encrypt environment variables, use VPC when needed

πŸ“‹ Quick Reference Card: Lambda Essentials

Max execution time15 minutes
Memory range128 MB - 10,240 MB
Deployment package50 MB zipped, 250 MB unzipped
Concurrency limit1,000 (default, can increase)
Environment variables4 KB total
Ephemeral storage (/tmp)512 MB - 10 GB
Invocation typesSynchronous, Asynchronous, Stream-based
Pricing (us-east-1)$0.20 per 1M requests + $0.0000166667 per GB-second
Free tier1M requests + 400,000 GB-seconds/month

🧠 Memory Aid: "SCALE" for Lambda Success

  • Stateless designβ€”store state externally
  • Cold startsβ€”optimize initialization
  • Asynchronous patternsβ€”use DLQs and retries
  • Least privilegeβ€”minimal IAM permissions
  • Events everywhereβ€”trigger from 50+ sources

πŸ“š Further Study

  1. AWS Lambda Developer Guide: https://docs.aws.amazon.com/lambda/latest/dg/welcome.html - Comprehensive official documentation with best practices

  2. Serverless Framework: https://www.serverless.com/framework/docs - Popular infrastructure-as-code tool for deploying Lambda functions

  3. AWS Lambda Power Tuning: https://github.com/alexcasalboni/aws-lambda-power-tuning - Open-source tool to optimize memory/cost configuration

πŸš€ Next Steps: Try building a simple serverless API using Lambda + API Gateway + DynamoDB. Start with the AWS Free Tierβ€”1 million Lambda requests per month are free forever!


Master serverless computing through practice. The best way to learn Lambda is to build real applicationsβ€”start small, iterate, and scale as you grow more confident with event-driven architectures.