AWS Lambda Python Tutorial Step by Step: Build Serverless Functions in 2026

AWS Lambda Python Tutorial Step by Step: Build Serverless Functions in 2026

Serverless computing has reshaped how we build and scale applications, and AWS Lambda remains the dominant force in this space. If you’re a Python developer looking to harness the power of AWS Lambda without wading through fragmented documentation, this step-by-step tutorial is written for you.

I’ve spent years deploying Python workloads to Lambda—first as a hesitant beginner fighting import errors, and now as someone who confidently ships event-driven architectures. This guide distills everything I’ve learned into a practical, walkthrough-based resource.

Let’s build something real together.


What Is AWS Lambda (and Why Python)?

AWS Lambda is a serverless compute service that runs your code in response to events—HTTP requests, file uploads, database changes, scheduled timers—without requiring you to provision or manage servers. You upload your code, AWS handles the rest: scaling, operating system maintenance, patching, and high availability.

Python is one of the most popular Lambda runtimes, and for good reason:

  • Readable syntax that’s friendly to newcomers and productive for veterans
  • Massive ecosystem via PyPI (pandas, requests, boto3, and thousands more)
  • Strong community support with abundant examples
  • First-class AWS SDK support through boto3

As of early 2026, AWS Lambda supports Python 3.12 and 3.13. We’ll use Python 3.13 throughout this tutorial.


Prerequisites: What You Need Before We Start

Before writing any code, make sure you have the following in place.

1. An AWS Account

If you don’t have one, sign up at aws.amazon.com. The AWS Free Tier includes 1 million Lambda requests and 400,000 GB-seconds of compute per month, which is more than enough for learning.

2. Python 3.13 Installed Locally

Verify your Python version:

python3 --version
# Python 3.13.1

If you need to install or upgrade Python, I recommend using pyenv for managing multiple versions:

pyenv install 3.13.1
pyenv local 3.13.1

3. AWS CLI v2 Installed and Configured

Install the AWS CLI v2 from the official installer, then configure it:

aws configure

You’ll be prompted for:

  • AWS Access Key ID
  • AWS Secret Access Key
  • Default region name (e.g., us-east-1)
  • Default output format (use json)

If you don’t have an access key, create one in the AWS Console under IAM → Users → [your user] → Security credentials. I strongly recommend setting up an IAM user specifically for development rather than using your root account credentials.

4. Basic Familiarity with Python

You should understand functions, dictionaries, and exception handling. Deep AWS expertise is not required—we’ll cover everything as we go.


Step 1: Creating Your First Lambda Function in the Console

Let’s start with the simplest possible path: creating a function directly in the AWS Management Console. This helps you understand the anatomy of a Lambda function before we move to more production-ready workflows.

  1. Log in to the AWS Console.
  2. Search for Lambda in the services search bar.
  3. Click Create function.

Configure the Function

Choose Author from scratch and fill in these fields:

  • Function name: hello-lambda
  • Runtime: Python 3.13
  • Architecture: x86_64 (default is fine)
  • Execution role: Create a new role with basic Lambda permissions

Click Create function. Within seconds, AWS provisions everything you need.

Replace the Default Code

After creation, scroll down to the Code source panel. Replace the placeholder code with:

import json

def lambda_handler(event, context):
    """
    A simple Lambda function that greets a user by name.
    Expects an event with a 'name' field in the query string or body.
    """
    # Try to extract the name from query string parameters first
    name = None
    if event.get('queryStringParameters'):
        name = event['queryStringParameters'].get('name')

    # Fall back to the JSON body if no query parameter
    if not name and event.get('body'):
        try:
            body = json.loads(event['body'])
            name = body.get('name')
        except (json.JSONDecodeError, TypeError):
            pass

    # Default greeting if no name was provided
    if not name:
        name = 'World'

    return {
        'statusCode': 200,
        'headers': {
            'Content-Type': 'application/json'
        },
        'body': json.dumps({
            'message': f'Hello, {name}!',
            'function': context.function_name,
            'request_id': context.aws_request_id
        })
    }

Click Deploy to save your changes.

Test the Function

Click Test and create a new test event. Use this JSON:

{
  "queryStringParameters": {
    "name": "Sarah"
  }
}

Save the event and click Test again. You should see output similar to:

{
  "statusCode": 200,
  "headers": {
    "Content-Type": "application/json"
  },
  "body": "{\"message\": \"Hello, Sarah!\", \"function\": \"hello-lambda\", \"request_id\": \"a1b2c3d4-...\"}"
}

Congratulations—you’ve deployed your first Lambda function. But this is just the beginning. Real applications require dependencies, version control, and repeatable deployments.


Step 2: Deploying Lambda Functions with the AWS CLI

The console is great for experimentation, but professional workflows use the CLI or Infrastructure as Code tools. Let’s deploy the same function using the AWS CLI.

Create a Project Structure

mkdir lambda-hello && cd lambda-hello
touch lambda_function.py

Place this in lambda_function.py:

import json

def lambda_handler(event, context):
    return {
        'statusCode': 200,
        'body': json.dumps({
            'message': 'Hello from the CLI!',
            'event_type': str(event.get('source', 'direct-invocation'))
        })
    }

Package and Zip the Function

zip function.zip lambda_function.py

Create the Function via CLI

First, you need an IAM role with the lambda:InvokeFunction permission managed by AWS. Create one:

# Create a trust policy file
cat > trust-policy.json << 'EOF'
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Service": "lambda.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}
EOF

# Create the role
aws iam create-role \
  --role-name lambda-basic-role \
  --assume-role-policy-document file://trust-policy.json

# Attach the basic execution policy
aws iam attach-role-policy \
  --role-name lambda-basic-role \
  --policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole

Wait a few seconds for the role to propagate, then create the function:

aws lambda create-function \
  --function-name hello-cli-lambda \
  --runtime python3.13 \
  --role arn:aws:iam::<YOUR_ACCOUNT_ID>:role/lambda-basic-role \
  --handler lambda_function.lambda_handler \
  --zip-file fileb://function.zip \
  --region us-east-1

Replace <YOUR_ACCOUNT_ID> with your 12-digit AWS account ID.

Invoke the Function

aws lambda invoke \
  --function-name hello-cli-lambda \
  --payload '{"source": "cli-test"}' \
  response.json

cat response.json
# {"statusCode": 200, "body": "{\"message\": \"Hello from the CLI!\", \"event_type\": \"cli-test\"}"}

Step 3: Adding External Dependencies with Layers

Pure Python will only take you so far. Eventually you’ll want libraries like requests, boto3, or domain-specific packages. Lambda has two main ways to include dependencies:

  1. Package them in the deployment zip (simple but bloats every deployment)
  2. Use Lambda Layers (share dependencies across functions)

Let’s create a layer that includes the httpx library.

Create the Layer Package

mkdir -p httpx-layer/python
cd httpx-layer/python

# Install the dependency into the local python folder
pip install httpx --target .

# Go back and zip the layer
cd ..
zip -r ../httpx-layer.zip python/
cd ..

Publish the Layer

aws lambda publish-layer-version \
  --layer-name httpx-layer \
  --zip-file fileb://httpx-layer.zip \
  --compatible-runtimes python3.13 python3.12 \
  --compatible-architectures x86_64

Note the LayerArn and Version in the output. Now attach the layer to your existing function:

aws lambda update-function-configuration \
  --function-name hello-cli-lambda \
  --layers arn:aws:lambda:us-east-1:<YOUR_ACCOUNT_ID>:layer:httpx-layer:1

Use the Layer in Your Code

Update lambda_function.py:

import json
import httpx

def lambda_handler(event, context):
    try:
        # Fetch a random piece of advice from a public API
        response = httpx.get('https://api.adviceslip.com/advice', timeout=10.0)
        response.raise_for_status()
        advice = response.json().get('slip', {}).get('advice', 'No advice today.')

        return {
            'statusCode': 200,
            'body': json.dumps({
                'advice': advice,
                'status': 'success'
            })
        }
    except Exception as e:
        return {
            'statusCode': 500,
            'body': json.dumps({
                'error': str(e),
                'status': 'failure'
            })
        }

Repackage and redeploy:

zip function.zip lambda_function.py
aws lambda update-function-code \
  --function-name hello-cli-lambda \
  --zip-file fileb://function.zip

Wait a moment for the update to finish, then invoke it:

aws lambda invoke \
  --function-name hello-cli-lambda \
  response.json

cat response.json
# {"statusCode": 200, "body": "{\"advice\": \"Never regret anything that made you smile.\", \"status\": \"success\"}"}

Step 4: Using the AWS SAM CLI for Production Deployments

The console and raw CLI are fine for learning, but AWS SAM (Serverless Application Model) is the right tool for real projects. SAM provides a YAML template that defines your entire serverless application as code.

Install SAM CLI

# macOS
brew install aws-sam-cli

# Linux/Windows: use the installer from the official SAM docs
sam --version
# SAM CLI, version 1.142.0

Bootstrap a New Project

sam init

Choose the following options interactively:

  • Template: AWS Quick Start Templates
  • Package type: Zip
  • Runtime: Python 3.13
  • Project name: sam-hello-app

This generates a project structure:

sam-hello-app/
├── template.yaml
├── hello_world/
│   ├── app.py
│   └── requirements.txt
├── events/
│   └── event.json
└── tests/
    └── unit/
        └── test_handler.py

Review the Template

Open template.yaml:

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: A simple Python Lambda application

Globals:
  Function:
    Timeout: 10
    MemorySize: 256
    Runtime: python3.13

Resources:
  HelloWorldFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: hello_world/
      Handler: app.lambda_handler
      Events:
        HelloWorld:
          Type: Api
          Properties:
            Path: /hello
            Method: get

Outputs:
  HelloWorldApi:
    Description: API Gateway endpoint URL
    Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/hello/"

Test Locally

SAM lets you run Lambda functions locally using Docker:

sam local invoke HelloWorldFunction --event events/event.json

You can also start a local API:

sam local start-api
# Visit http://localhost:3000/hello

Deploy to AWS

sam deploy --guided

Answer the prompts (stack name, region, confirm changes). After the first deploy, subsequent deployments are simpler:

sam deploy

Common Pitfalls and How to Avoid Them

Pitfall 1: The “Unable to Import Module” Error

This is the most common Lambda error, and it almost always stems from one of these causes:

Cause: Incorrect handler path. The handler must match <filename>.<function_name>. If your file is app.py and the function is lambda_handler, the handler is app.lambda_handler.

Cause: Dependencies not packaged correctly. When you zip a deployment package, the dependencies must live in the root of the zip, not inside a nested folder.

Fix: Always verify the structure of your zip:

unzip -l function.zip | head -20

You should see requests/, urllib3/, and similar package folders at the top level alongside your handler file.

Pitfall 2: Lambda Timeout Errors

Lambda has a maximum execution timeout of 15 minutes. Functions that make slow HTTP requests or process large datasets frequently hit timeouts.

Fix: Set realistic timeouts and implement circuit breakers:

import os
import httpx

def lambda_handler(event, context):
    # Get remaining time and use it as our timeout
    remaining_ms = context.get_remaining_time_in_millis()
    timeout = min(remaining_ms / 1000 - 1, 30)  # Leave 1 second buffer

    try:
        with httpx.Client(timeout=timeout) as client:
            response = client.get('https://slow-api.example.com/data')
            return {'statusCode': 200, 'body': response.text}
    except httpx.TimeoutException:
        return {'statusCode': 504, 'body': 'Upstream timeout'}

Pitfall 3: Cold Start Latency

When a Lambda function hasn’t been invoked recently, AWS spins up a new container. This “cold start” adds latency—typically 100-500ms for Python.

Fix: Initialize expensive resources outside the handler so they persist across invocations:

import boto3
import httpx

# These run once per container, not per invocation
dynamodb = boto3.resource('dynamodb')
table = dynamodb.Table(os.environ['TABLE_NAME'])
http_client = httpx.Client(timeout=30)

def lambda_handler(event, context):
    # Reuse the table and http_client here
    pass

Pitfall 4: Forgetting to Handle Pagination in boto3

This is a subtle but painful bug. If you call table.scan() on a large DynamoDB table, you’ll silently receive only the first megabyte of data.

Fix: Always use the paginator:

def get_all_items(table_name):
    client = boto3.client('dynamodb')
    paginator = client.get_paginator('scan')

    all_items = []
    for page in paginator.paginate(TableName=table_name):
        all_items.extend(page.get('Items', []))

    return all_items

Pitfall 5: Environment Variables That Exceed Size Limits

Lambda allows up to 4 KB of environment variables per function. If you try to stuff large configuration blobs in there, deployments will fail.

Fix: Use AWS Systems Manager Parameter Store or Secrets Manager for large values:

import boto3
import json

_ssm = boto3.client('ssm')
_config_cache = None

def get_config():
    global _config_cache
    if _config_cache is None:
        response = _ssm.get_parameter(
            Name='/myapp/config',
            WithDecryption=True
        )
        _config_cache = json.loads(response['Parameter']['Value'])
    return _config_cache

Real-World Use Cases for Python Lambda Functions

Use Case 1: Automated Image Processing on S3 Upload

Trigger a Lambda when a user uploads a photo to S3, resize it, and store multiple sizes.

“`python
import boto3
import io
from PIL import Image

s3 = boto3.client(‘s3’)

def lambda_handler(event, context):
bucket = event[‘Records’][0][‘s3’][‘bucket’][‘name’]
key = event[‘Records’][0][‘s3

Leave a Reply

Your email address will not be published. Required fields are marked *