Write and Run AWS Lambda Functions
Write and run AWS Lambda functions in this AWS Tutorial tutorial — hands-on steps, troubleshooting, and what to study next.
Focus: write and run aws lambda functions
You've built APIs, stored files in S3, and secured access with IAM — but every time you deploy a server just to run a few lines of code, you feel the weight of maintenance. That's the pain this lesson kills: the overhead of provisioning, patching, and scaling infrastructure for simple functions. By the end, you’ll write and run AWS Lambda functions — serverless compute that scales automatically and costs pennies — and you’ll see why Lambda is the backbone of modern event-driven backends.
The problem this lesson solves
Traditional cloud computing means renting servers — EC2 instances, containers, or even Kubernetes clusters — and then managing them for the rest of your life. You handle OS patches, security updates, auto-scaling rules, and idle capacity. If your function runs once a day, you still pay for 24/7 uptime. That's a waste of money and mental energy.
Serverless computing flips this model. You upload a function, and AWS runs it on demand, scaling to zero when there's no traffic. You don't see the servers, you don't manage them, and you pay only for the milliseconds your code actually executes. This is brilliant for:
- Event-driven tasks like resizing images when a file lands in S3.
- API backends where each request triggers a stateless function.
- Cron-like scheduled jobs (e.g., nightly database cleanups).
But serverless also brings new challenges: cold starts, execution time limits, and statelessness. Without understanding these, you'll build functions that fail in production. This lesson gives you a rock-solid mental model and hands-on experience to avoid those pitfalls.
Core concept / mental model
Think of AWS Lambda as a vending machine for code. You drop in a coin (an event — an HTTP request, a new file in S3, a scheduled timer), and the machine dispenses a snack (your function's output). You don't know which vending machine, where it is, or how many exist — you just know that when you insert a coin, you get a snack. That's the power of abstraction.
Under the hood, Lambda is a Function-as-a-Service (FaaS) platform. You write a handler function — a simple Python method — and AWS packages it with a runtime (Python 3.10+, Node, etc.) and executes it in a sandboxed container. The container is reused for multiple invocations to reduce latency, but you must treat every invocation as if it's the first: stateless by design.
Here's the anatomy of a Lambda function:
- Handler: The entry point, e.g.,
lambda_function.lambda_handler. It receives two arguments:event(input data) andcontext(runtime metadata). - Runtime: The language and version AWS uses to run your code.
- Trigger: The event source that invokes your function (S3, API Gateway, CloudWatch Events, etc.).
- Role: An IAM role that grants your function permissions to access other AWS services.
- Configuration: Memory, timeout, environment variables, etc.
Event-driven architecture is the core pattern. Your function responds to events rather than polling for them. This is radically different from traditional request-response servers — Lambda functions are short-lived and specialized for a single job.
How it works step by step
Let's map the lifecycle of a Lambda invocation:
- Create or update the function: You write your code, package it (ZIP or container image), and upload it via the AWS Console, CLI, or infrastructure-as-code tools like CloudFormation.
- Configure the trigger: You attach an event source (e.g., an S3 bucket
s3:ObjectCreatedevent or an API Gateway endpoint). Each trigger type has its own event schema. - Invocation: AWS continuously watches for events. When one arrives, it finds an available execution environment (a warm container) or creates a new one (cold start).
- Execution: Your handler runs, receives the
eventobject, and returns a response. If your code synchronously invokes, the caller gets the response; if asynchronously, AWS queues the event and returns a success immediately. - Billing & scaling: AWS measures the time your code runs (rounded to the nearest millisecond) and multiplies by memory allocated. It auto-scales to thousands of concurrent executions based on event volume.
Cold starts happen when a new container is created — this adds latency (100ms–1s) and is a key trade-off. Warming techniques (provisioned concurrency) can mitigate this, but at a cost. For most use cases, cold starts are acceptable if you design for eventual consistency.
Statelessness is crucial: you can't store local files or in-memory data between invocations. Use external services like S3, DynamoDB, or ElastiCache for persistence. Never assume your code will run on the same machine twice.
Hands-on walkthrough
Let's build a simple Lambda function step by step — from the AWS Console and then with the AWS CLI, so you see both paths.
Console quick start
- Go to Lambda in the AWS Console and click Create function.
- Choose Author from scratch, give it a name like
my-first-function. - Set the Runtime to Python 3.10 (or later), and Architecture to x86_64 (or arm64 for cost savings).
- For Permissions, select Create a new role with basic Lambda permissions (Lambda will create a role with
AWSLambdaBasicExecutionRole). - Click Create function.
You'll see an inline code editor with a default lambda_function.py. Replace it with this:
# lambda_function.py
def lambda_handler(event, context):
"""Simple handler that echoes a greeting."""
name = event.get('name', 'World')
return {
'statusCode': 200,
'body': f"Hello, {name}! Request ID: {context.aws_request_id}"
}
Click Deploy, then Test. Configure a test event with this JSON:
{"name": "AWS"}
Click Test again. You should see a response like:
{
"statusCode": 200,
"body": "Hello, AWS! Request ID: 12345678-..."
}
You just wrote and ran your first Lambda function!
CLI deployment with aws lambda
The Console is great for learning, but real projects use the CLI. First, create a local file:
# lambda_function.py
def lambda_handler(event, context):
print(f"Received event: {event}")
return {"statusCode": 200, "body": "CLI success"}
Then package and deploy:
# Zip the code (include any dependencies, but for this simple case just the two files)
zip function.zip lambda_function.py
# Create the Lambda function (if not already existing)
aws lambda create-function \
--function-name my-cli-function \
--runtime python3.10 \
--role arn:aws:iam::123456789012:role/lambda-execution-role \
--handler lambda_function.lambda_handler \
--zip-file fileb://function.zip
# Invoke it synchronously and capture the output
aws lambda invoke \
--function-name my-cli-function \
--payload '{"key":"value"}' \
response.json
cat response.json
Expected output in response.json:
{"statusCode": 200, "body": "CLI success"}
Pro tip: If you get a
--roleerror, create the IAM role first and make sure it has theAWSLambdaBasicExecutionRolepolicy attached. The role ARN starts witharn:aws:iam::. You can also useaws iam get-roleto find it.
Triggering with S3 events
Now let's attach a practical trigger — an S3 bucket that resizes images. This is a classic serverless use case. Create a new function that responds to S3 uploads:
import boto3
from PIL import Image
import os
s3 = boto3.client('s3')
def lambda_handler(event, context):
# Extract bucket and object key from the S3 event
bucket = event['Records'][0]['s3']['bucket']['name']
key = event['Records'][0]['s3']['object']['key']
# Download the image to /tmp (Lambda's writable space)
download_path = f'/tmp/{os.path.basename(key)}'
s3.download_file(bucket, key, download_path)
# Resize the image (e.g., max width 300px)
with Image.open(download_path) as img:
img.thumbnail((300, 300))
upload_path = '/tmp/resized-' + os.path.basename(key)
img.save(upload_path)
# Upload the resized image to a destination bucket
destination_bucket = os.environ.get('DEST_BUCKET', 'my-resized-images')
s3.upload_file(upload_path, destination_bucket, os.path.basename(key))
return {"statusCode": 200, "body": "Image resized"}
Blockquote: Notice the use of
/tmp— it's the only local storage you have, and it's ephemeral. Never store critical data there. Also, environment variables likeDEST_BUCKETare a clean way to configure your function without changing code.
You'll need to add the Pillow library as a dependency. Use a Lambda Layer or package it into a deployment package (with pip install -t .). A simpler approach: use the AWS Console's Code tab, navigate to Layers, and add the AWSLambda-Python3-Pillow layer from the AWS community.
Compare options / when to choose what
Lambda is powerful, but it's not the only compute option. Here's a comparison to help you choose:
| Option | Pros | Cons | Best for |
|---|---|---|---|
| AWS Lambda | No servers, auto-scale, pay per use, event-driven | Cold starts, execution limit (15 min), stateless | Event-driven tasks, short-running APIs, cron jobs |
| Amazon ECS (Fargate) | Longer tasks, stateful, custom runtimes | Need to manage clusters, more configuration | Microservices running 24/7, batch jobs >15 min |
| EC2 instances | Full control, any OS, persistent | Manual scaling, patching, cost | Legacy apps, long-running processes, heavy compute |
Key decision factors:
- Execution time: Lambda hard limit is 15 minutes (and default is 3 seconds). If your job runs longer, use Fargate or EC2.
- State: If you need to maintain state in memory, Lambda's stateless model forces you to externalize it.
- Scaling: Lambda scales almost infinitely with no effort, but you pay extra for concurrency spikes.
- Cost: For sporadic/light workloads, Lambda is far cheaper. For steady high utilization, a fixed instance might be cheaper.
When to choose Lambda: If you're building event-driven systems (S3 triggers, API Gateway endpoints, IoT rules), you want zero ops, and your tasks are quick. It's the default for many backend microservices in event-driven architectures.
Troubleshooting & edge cases
You're going to hit errors. Here are the most common ones and how to fix them:
-
Wrong handler name: "Handler 'lambda_function.handler' not found" — Ensure the handler field matches your file name and function name (e.g.,
lambda_function.lambda_handler). Check for Python indentation errors. -
Missing IAM role permissions: "AccessDeniedException" when your function tries to access S3. Attach a policy that grants
s3:PutObject(or whatever you need) to the role. -
Timeout: If your function times out, increase the Timeout setting in the configuration (up to 900 seconds). Also check for infinite loops or inefficient code.
-
Memory errors: "Task timed out after 3.00 seconds" often means your code is too slow or you need more memory. Lambda allocates CPU proportional to memory, so bump the memory (e.g., 128MB → 512MB) to improve performance.
-
Cold start latency: If you see slow first invocations, enable Provisioned Concurrency to keep a number of instances warm. It costs extra but eliminates cold starts.
-
Environment variable issues: Variables must be strings. If you need a number, parse it in code. Also, be aware that environment variables are encrypted — use AWS KMS for custom keys.
-
Event schema guessing: When debugging, always print the event to see its actual structure. For S3 events, the structure is different from API Gateway. Use
print(event)to inspect it. -
Statelessness gotcha: If you write to
/tmpand then read it in a subsequent invocation, you might get stale data (or no data) because a different container may be used. Use S3 or ElastiCache for persistence.
What you learned & what's next
You've learned the core concept of write and run AWS Lambda functions — from creating a simple Hello World to event-driven workloads like image resizing. You now understand the mental model of vending machine code, the step-by-step invocation lifecycle, and how to choose between Lambda, Fargate, and EC2. You've seen hands-on examples in both the Console and CLI, and you know how to troubleshoot common pitfalls.
Next lesson: You're ready to explore AWS API Gateway to expose your Lambda functions as REST APIs, or dive into S3 event notifications to trigger functions automatically. This Lambda knowledge is the foundation for building fully serverless backends — the next step is connecting Lambda to the outside world.
Practice recap
Now apply what you learned: create a Lambda function that's triggered by an S3 upload and logs the file size. Use the AWS CLI to deploy it, set up an S3 trigger, and test it by uploading a sample file. This reinforces the event-driven model and IAM configuration — both essential for real serverless projects.
Common mistakes
- Forgetting to set the correct handler in the Lambda configuration — always align it with your filename and function name (e.g.,
lambda_function.lambda_handler). - Assuming Lambda functions are stateful — always use external storage for anything that must persist across invocations.
- Ignoring timeout and memory settings — tasks that exceed the limit fail silently; set them based on real needs.
- Not granting the Lambda role the necessary IAM permissions, leading to cryptic AccessDenied errors when accessing other AWS services.
Variations
- Deploy Lambda via AWS SAM or CloudFormation — infrastructure as code for repeatable deployments.
- Use Lambda Layers to manage shared dependencies and reduce deployment package size.
- Write functions in container images instead of ZIP packages — useful for large dependencies or custom runtimes.
Real-world use cases
- Automatically resize images uploaded to S3 and store them in a separate bucket for web delivery.
- Build a REST API with Lambda and API Gateway as a serverless backend for a mobile app.
- Run a scheduled job (via CloudWatch Events) that cleans up old files or sends daily email reports.
Key takeaways
- AWS Lambda runs your code without servers, scaling to zero and billing per invocation, making it ideal for event-driven tasks.
- The handler signature
lambda_handler(event, context)is the entry point — understand theeventstructure for your trigger. - Lambda is stateless; always offload state to services like S3 or DynamoDB.
- Cold starts add latency; use provisioned concurrency for latency-sensitive applications.
- Choose Lambda for short, infrequent, or auto-scaling workloads; use containers/EC2 for long-running or stateful processes.
- Troubleshoot by checking timeouts, memory, IAM permissions, and the event schema via logging.
Keep learning
Related tutorials, quizzes, and articles for this topic.
Discussion
Questions, corrections, and tips help everyone reading this page.
0 comments
Add a comment
No comments yet — start the thread.