Overview
AWS Lambda is a serverless compute service that lets you run code without provisioning or managing servers. This tutorial covers function creation, event-driven architecture, and deployment patterns you need for the AWS Solutions Architect Associate (SAA-C03) exam.
Prerequisites:
- AWS account with appropriate IAM permissions
- Python 3.9+ installed locally
- AWS CLI configured with credentials
- Basic understanding of Python and JSON
- Familiarity with AWS IAM and IAM roles
Why This Matters for SAA-C03: Lambda is central to serverless architecture design. The exam tests your ability to design scalable, cost-effective solutions using Lambda with various AWS services like S3, DynamoDB, API Gateway, and CloudWatch.
Getting Started
Step 1: Install AWS CLI and Configure Credentials
# Verify AWS CLI installation
aws --version
# Configure credentials (you'll be prompted for Access Key ID and Secret Access Key)
aws configure
# Test your configuration
aws sts get-caller-identityStep 2: Create Your First Lambda Function
# Create a project directory
mkdir lambda-tutorial
cd lambda-tutorial
# Create the function code
cat > lambda_function.py << 'EOF'
import json
import logging
logger = logging.getLogger()
logger.setLevel(logging.INFO)
def lambda_handler(event, context):
"""
Simple Lambda handler that processes S3 events
event: AWS Lambda event object
context: AWS Lambda context object
"""
try:
logger.info(f"Received event: {json.dumps(event)}")
# Extract bucket and key from S3 event
bucket = event['Records'][0]['s3']['bucket']['name']
key = event['Records'][0]['s3']['object']['key']
response = {
'statusCode': 200,
'body': json.dumps({
'message': f'Processed {key} from {bucket}',
'bucket': bucket,
'key': key
})
}
return response
except Exception as e:
logger.error(f"Error processing event: {str(e)}")
return {
'statusCode': 500,
'body': json.dumps({'error': str(e)})
}
EOFStep 3: Package and Deploy
# Create deployment package
zip lambda_deployment.zip lambda_function.py
# Create IAM role for Lambda
aws iam create-role \
--role-name lambda-execution-role \
--assume-role-policy-document '{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": {"Service": "lambda.amazonaws.com"},
"Action": "sts:AssumeRole"
}]
}'
# Attach basic execution policy
aws iam attach-role-policy \
--role-name lambda-execution-role \
--policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
# Wait for role to be available (important!)
sleep 10
# Deploy the function
aws lambda create-function \
--function-name my-first-lambda \
--runtime python3.11 \
--role arn:aws:iam::ACCOUNT_ID:role/lambda-execution-role \
--handler lambda_function.lambda_handler \
--zip-file fileb://lambda_deployment.zipLambda Functions and Event Handling
Understanding the Lambda Handler: The handler is the entry point for Lambda execution. It receives two parameters: event (contains data from the trigger) and context (provides runtime information).
# Advanced handler with multiple event types
import json
import boto3
from datetime import datetime
s3_client = boto3.client('s3')
dynamodb = boto3.resource('dynamodb')
def lambda_handler(event, context):
"""
Handle multiple AWS event sources
"""
# Extract source from context
request_id = context.request_id
function_name = context.function_name
remaining_time = context.get_remaining_time_in_millis()
print(f"Function: {function_name}, Request ID: {request_id}")
print(f"Remaining time: {remaining_time}ms")
# Determine event source
if 'Records' in event:
if 's3' in event['Records'][0]:
return handle_s3_event(event)
elif 'dynamodb' in event['Records'][0]:
return handle_dynamodb_stream(event)
elif 'sns' in event['Records'][0]:
return handle_sns_event(event)
elif 'requestContext' in event:
return handle_api_gateway_event(event)
elif 'source' in event and event['source'] == 'aws.events':
return handle_scheduled_event(event)
return {'statusCode': 400, 'body': 'Unknown event source'}
def handle_s3_event(event):
"""Process S3 bucket events"""
results = []
for record in event['Records']:
bucket = record['s3']['bucket']['name']
key = record['s3']['object']['key']
results.append({'bucket': bucket, 'key': key})
return {'statusCode': 200, 'body': json.dumps(results)}
def handle_dynamodb_stream(event):
"""Process DynamoDB stream records"""
for record in event['Records']:
if record['eventName'] == 'INSERT':
new_image = record['dynamodb']['NewImage']
print(f"New item: {new_image}")
elif record['eventName'] == 'MODIFY':
old_image = record['dynamodb']['OldImage']
new_image = record['dynamodb']['NewImage']
print(f"Modified from {old_image} to {new_image}")
return {'statusCode': 200, 'body': 'Stream processed'}
def handle_sns_event(event):
"""Process SNS notifications"""
for record in event['Records']:
message = record['Sns']['Message']
print(f"SNS Message: {message}")
return {'statusCode': 200, 'body': 'SNS processed'}
def handle_api_gateway_event(event):
"""Handle API Gateway requests"""
method = event['httpMethod']
path = event['path']
body = json.loads(event.get('body', '{}'))
return {
'statusCode': 200,
'headers': {'Content-Type': 'application/json'},
'body': json.dumps({
'method': method,
'path': path,
'received': body
})
}
def handle_scheduled_event(event):
"""Handle CloudWatch scheduled events"""
print(f"Scheduled event triggered at {datetime.now()}")
# Perform periodic tasks
return {'statusCode': 200, 'body': 'Scheduled task completed'}Triggers, Configuration, and Best Practices
Configuring Lambda Triggers: Triggers automatically invoke your Lambda function when specific events occur in AWS services.
# Configure S3 trigger using AWS CLI
aws lambda add-permission \
--function-name my-first-lambda \
--statement-id AllowS3Invoke \
--action lambda:InvokeFunction \
--principal s3.amazonaws.com \
--source-arn arn:aws:s3:::my-bucket
# Add S3 bucket notification configuration
aws s3api put-bucket-notification-configuration \
--bucket my-bucket \
--notification-configuration '{
"LambdaFunctionConfigurations": [{
"LambdaFunctionArn": "arn:aws:lambda:us-east-1:ACCOUNT_ID:function:my-first-lambda",
"Events": ["s3:ObjectCreated:*"],
"Filter": {
"Key": {"FilterRules": [{"Name": "prefix", "Value": "uploads/"}]}
}
}]
}'
# Configure API Gateway trigger
aws apigateway create-rest-api \
--name lambda-api \
--description "API Gateway for Lambda"
# Set up CloudWatch scheduled event
aws events put-rule \
--name lambda-daily-task \
--schedule-expression "cron(0 2 * * ? *)" \
--state ENABLED
aws events put-targets \
--rule lambda-daily-task \
--targets "Id"="1","Arn"="arn:aws:lambda:us-east-1:ACCOUNT_ID:function:my-first-lambda"Production Best Practices:
# Best practices template
import os
import json
import logging
from functools import lru_cache
import boto3
from botocore.exceptions import ClientError
# Configure logging
logger = logging.getLogger()
logger.setLevel(os.getenv('LOG_LEVEL', 'INFO'))
# Cache AWS client initialization outside handler for reuse
@lru_cache(maxsize=None)
def get_s3_client():
return boto3.client('s3')
def lambda_handler(event, context):
"""
Production-ready Lambda handler
- Uses environment variables
- Implements error handling and retries
- Includes proper logging
- Manages timeouts
"""
try:
# Get environment variables
bucket = os.environ.get('BUCKET_NAME')
max_retries = int(os.environ.get('MAX_RETRIES', 3))
# Check remaining execution time
remaining_ms = context.get_remaining_time_in_millis()
if remaining_ms < 5000: # Less than 5 seconds remaining
logger.warning('Insufficient time remaining for execution')
return {'statusCode': 429, 'body': 'Insufficient execution time'}
# Process event
logger.info(f"Processing event with remaining time: {remaining_ms}ms")
result = process_with_retry(event, bucket, max_retries)
return {
'statusCode': 200,
'body': json.dumps({'message': 'Success', 'result': result})
}
except ClientError as e:
logger.error(f"AWS error: {e.response['Error']['Code']} - {e.response['Error']['Message']}")
return {'statusCode': 500, 'body': json.dumps({'error': 'AWS service error'})}
except Exception as e:
logger.error(f"Unexpected error: {str(e)}", exc_info=True)
return {'statusCode': 500, 'body': json.dumps({'error': 'Internal server error'})}
def process_with_retry(event, bucket, max_retries):
"""Implement retry logic"""
for attempt in range(max_retries):
try:
return perform_operation(event, bucket)
except ClientError as e:
if attempt == max_retries - 1:
raise
logger.warning(f"Attempt {attempt + 1} failed, retrying...")
def perform_operation(event, bucket):
"""Actual business logic"""
s3 = get_s3_client()
# Implementation here
return {'processed': True}Exam Tips for AWS Solutions Architect Associate (SAA-C03)
Key Lambda Concepts for the Exam:
- Concurrency limits: Default concurrent execution limit is 1,000 per account per region. Reserved concurrency guarantees capacity for critical functions
- Cold starts: First invocation takes longer (initialization). Provisioned concurrency keeps instances warm
- Timeout settings: Maximum execution time is 15 minutes. Functions must return before timeout or they fail
- Memory allocation: RAM ranges from 128MB to 10,240MB. CPU allocation scales with memory. More memory = better performance = higher cost
- Event sources: Synchronous (API Gateway, ALB, CloudFront) vs asynchronous (S3, SNS, SQS, DynamoDB Streams)
- Dead-letter queues (DLQ): Capture failed async invocations for later analysis
Common Exam Scenarios:
- Scenario: "Build a real-time image processing pipeline for uploaded images." Answer: S3 → Lambda trigger → process image → store result in S3/DynamoDB
- Scenario: "Scale a processing system with unpredictable traffic." Answer: Lambda with SQS for decoupling + reserved concurrency for predictable workload
- Scenario: "Run scheduled daily reports." Answer: CloudWatch Events rule on cron schedule → Lambda function
- Scenario: "Minimize cold start latency for critical APIs." Answer: Use provisioned concurrency or increase allocated memory
Summary
Key Takeaways:
- Lambda is serverless compute perfect for event-driven architectures
- Master multiple event sources: S3, API Gateway, DynamoDB Streams, SNS, SQS, CloudWatch Events
- Always implement proper error handling, logging, and retry logic in production
- Understand concurrency, timeouts, and memory allocation for cost optimization
- Use IAM roles with least-privilege permissions for security
- Cache AWS SDK clients outside the handler for performance
Next Steps:
- Practice deploying functions with AWS SAM (Serverless Application Model) for infrastructure as code
- Explore Lambda layers for code reuse and dependencies
- Study Lambda integration with API Gateway for REST API development
- Review the official AWS Lambda Developer Guide and exam guide for SAA-C03
For comprehensive SAA-C03 preparation, visit our AWS Solutions Architect Associate Certification Guide.

