AWS Lambda turned ten in 2025, and 2026 has been its busiest year of updates yet. AWS added .NET 10 support in January, raised the asynchronous payload limit from 256 KB to 1 MB in the first quarter, and pushed Lambda Managed Instances, with up to 32 GB of memory, three times the standard ceiling, into general availability by the second quarter. Response streaming is now live in every commercial AWS Region. None of that matters much, though, if you have never actually deployed an AWS Lambda function that does real work.
This tutorial builds a working serverless REST API from a blank AWS account: a task-management backend with five endpoints, backed by DynamoDB, fronted by Amazon API Gateway, and secured with a least-privilege IAM role. You write the Python handler yourself, deploy it with the AWS CLI, then redeploy it with AWS SAM, and finish with a stack you can actually extend. Budget about 60 minutes if you already have an AWS account, closer to 90 if you are setting one up for the first time.
Don't miss new tech stories on Google
Add Tech Insider once in the Google app and our stories appear in your news suggestions.
What You’ll Build in This Tutorial
By the end of this guide you will have a working Task Manager API running entirely on managed AWS services, with no servers to patch and no idle compute to pay for. The project follows the same pattern most production serverless APIs use in 2026: a single AWS Lambda function handling multiple routes, a DynamoDB table for storage, and API Gateway acting as the front door.
Your finished API will expose five routes:
- POST /tasks creates a new task
- GET /tasks lists every task
- GET /tasks/{id} retrieves a single task
- PUT /tasks/{id} updates a task
- DELETE /tasks/{id} removes a task
The stack behind those routes: one Lambda function running Python 3.13, a DynamoDB table in on-demand mode, an API Gateway REST API using Lambda proxy integration, an IAM role scoped to exactly the permissions the function needs, and an AWS SAM template that redeploys the whole thing with a single command. Every piece maps to a real production pattern, not a simplified version you will have to rebuild later once traffic shows up.
Prerequisites: Tools, Accounts, and Versions You Need
Install and confirm each item below before starting Step 1. Version mismatches are the most common reason a tutorial like this breaks halfway through.
| Tool | Version | Purpose | Check Command |
|---|---|---|---|
| AWS account | Active, billing enabled | Hosts every resource you create | N/A |
| AWS CLI | v2 (latest) | Deploys Lambda, API Gateway, and DynamoDB from the terminal | aws --version |
| Python | 3.13 | Local testing and the Lambda runtime itself | python3 --version |
| AWS SAM CLI | Latest version | Deploys the full stack from one template in Step 10 | sam --version |
| IAM permissions | Administrator or scoped policy | Create roles, functions, APIs, and tables | N/A |
| Code editor | Any | Write lambda_function.py and template.yaml | N/A |
If you have never installed the AWS CLI, grab it from the official AWS documentation and run aws configure once you have an access key. A free-tier AWS account covers this entire tutorial. The resources you create here fall well inside the 1 million monthly Lambda requests, 400,000 GB-seconds of compute, and 25 GB of DynamoDB storage that AWS includes at no charge, according to AWS’s published Lambda pricing.
How AWS Lambda and API Gateway Fit Together
Before writing code, it helps to know what each service actually does. Skipping this part is why so many people get stuck between Step 6 and Step 7, staring at a 502 error with no idea which service caused it.
The Core Components
Four services do all the work in this tutorial. Lambda runs your code on demand and charges only for the milliseconds it executes. API Gateway receives HTTPS requests, matches them to a route, and forwards each one to Lambda as a structured JSON event. DynamoDB stores your task records as key-value items with single-digit-millisecond reads. IAM issues the role that lets Lambda talk to DynamoDB and write logs, and without it every database call fails with an access-denied error.
Requests flow in one direction. A client sends an HTTPS request to API Gateway, API Gateway packages it as an event and invokes Lambda, Lambda runs your handler and returns a JSON response, and API Gateway translates that response back into an HTTP reply. The round trip typically finishes in under 200 milliseconds once a function is warm.
REST API vs HTTP API vs Lambda Function URLs
API Gateway offers three ways to expose an AWS Lambda function, and picking the wrong one costs time later. REST APIs, the type this tutorial uses, support usage plans, API keys, request validation, and resource policies, which makes them the standard choice for a production API you plan to secure and meter. HTTP APIs trade some of those features for lower latency and a lower price per million calls, which suits internal or high-volume traffic. Lambda function URLs skip API Gateway entirely and give a function its own HTTPS endpoint, the fastest option to set up but without the throttling or API-key controls Step 9 relies on.
What’s New in AWS Lambda for 2026
A few 2026 changes are worth knowing before you start, because they affect decisions you will make in later steps. AWS’s Q2 2026 serverless roundup confirmed that Lambda Managed Instances now support up to 32 GB of memory, three times the standard 10,240 MB ceiling, aimed at steady, memory-heavy workloads rather than bursty ones. The same update made response streaming available in every commercial AWS Region and brought the Lambda durable functions SDK for Java to general availability, joining Python and TypeScript.
Earlier in the year, AWS’s Q1 2026 roundup raised the maximum payload for asynchronous Lambda invocations, Amazon SQS, and EventBridge from 256 KB to 1 MB, and a January 2026 weekly roundup added .NET 10 as a managed runtime and container base image. AWS also introduced Lambda MicroVMs this year, isolated and stateful execution environments built for user- or AI-generated code, with sessions that can persist for up to 8 hours. None of these are required for the Task Manager API you are about to build, but they matter once you outgrow the basics, and this guide points back to them in the advanced tips section.
Step 1: Create an AWS Account and Configure the CLI
If you already have an AWS account and a configured CLI profile, skip to Step 2. Otherwise, sign up at aws.amazon.com, create an IAM user with programmatic access instead of using your root account, and generate an access key pair for that user. Root credentials should never touch a terminal.
Once the AWS CLI is installed, run aws configure and paste in your access key, secret key, and preferred region. Confirm everything works with a single identity check.
$ aws configure
AWS Access Key ID [None]: AKIAIOSFODNN7EXAMPLE
AWS Secret Access Key [None]: ****************EXAMPLE
Default region name [None]: us-east-1
Default output format [None]: json
$ aws sts get-caller-identity
{
"UserId": "AIDAEXAMPLE123456",
"Account": "123456789012",
"Arn": "arn:aws:iam::123456789012:user/your-username"
}
If that command returns your account ID instead of an error, the CLI is authenticated and ready for every step that follows.
Step 2: Set Up an IAM Role With Least-Privilege Permissions
Lambda needs an execution role before it can run. Resist the urge to attach a broad managed policy to save time. Create a role that trusts only the Lambda service, then attach exactly the permissions the Task Manager function needs: basic execution logging and access to one DynamoDB table.
// trust-policy.json
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": { "Service": "lambda.amazonaws.com" },
"Action": "sts:AssumeRole"
}
]
}
$ aws iam create-role \
--role-name task-manager-lambda-role \
--assume-role-policy-document file://trust-policy.json
$ aws iam attach-role-policy \
--role-name task-manager-lambda-role \
--policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
$ aws iam put-role-policy \
--role-name task-manager-lambda-role \
--policy-name DynamoDBTaskAccess \
--policy-document file://dynamodb-policy.json
The dynamodb-policy.json file referenced above should list only the five actions the function actually calls: PutItem, GetItem, UpdateItem, DeleteItem, and Scan, scoped to the ARN of the tasks table you create in Step 8. That scoping is what turns “it works” into “it works and can’t be abused if the function is ever compromised.”
Step 3: Write Your First AWS Lambda Function
This is the core of the tutorial: a single Python handler that routes all five endpoints based on the HTTP method and path parameters API Gateway sends it. Save this as lambda_function.py.
import json
import os
import uuid
import boto3
dynamodb = boto3.resource("dynamodb")
table = dynamodb.Table(os.environ["TABLE_NAME"])
def handler(event, context):
method = event["httpMethod"]
path_params = event.get("pathParameters") or {}
task_id = path_params.get("id")
try:
if method == "POST":
return create_task(event)
if method == "GET" and task_id:
return get_task(task_id)
if method == "GET":
return list_tasks()
if method == "PUT" and task_id:
return update_task(task_id, event)
if method == "DELETE" and task_id:
return delete_task(task_id)
return respond(400, {"error": "Unsupported route"})
except Exception as exc:
return respond(500, {"error": str(exc)})
def create_task(event):
body = json.loads(event["body"] or "{}")
item = {
"id": str(uuid.uuid4()),
"title": body.get("title", "Untitled task"),
"done": False,
}
table.put_item(Item=item)
return respond(201, item)
def list_tasks():
result = table.scan()
return respond(200, result.get("Items", []))
def get_task(task_id):
result = table.get_item(Key={"id": task_id})
item = result.get("Item")
if not item:
return respond(404, {"error": "Task not found"})
return respond(200, item)
def update_task(task_id, event):
body = json.loads(event["body"] or "{}")
table.update_item(
Key={"id": task_id},
UpdateExpression="SET title = :t, done = :d",
ExpressionAttributeValues={
":t": body.get("title", "Untitled task"),
":d": body.get("done", False),
},
)
return respond(200, {"id": task_id, "updated": True})
def delete_task(task_id):
table.delete_item(Key={"id": task_id})
return respond(204, {})
def respond(status_code, body):
return {
"statusCode": status_code,
"headers": {"Content-Type": "application/json"},
"body": json.dumps(body),
}
Notice that respond() always returns a dictionary with statusCode, headers, and a stringified body. That exact shape is not optional. API Gateway’s proxy integration expects it, and returning anything else is the single most common cause of a 502 error, covered in the troubleshooting section further down.
Step 4: Package and Deploy the Function via AWS CLI
Zip the function from inside its own directory, so lambda_function.py sits at the root of the archive rather than inside a subfolder, then create the function with the role ARN from Step 2.
$ zip function.zip lambda_function.py
adding: lambda_function.py (deflated 61%)
$ aws lambda create-function \
--function-name task-manager-api \
--runtime python3.13 \
--handler lambda_function.handler \
--role arn:aws:iam::123456789012:role/task-manager-lambda-role \
--zip-file fileb://function.zip \
--timeout 10 \
--memory-size 256 \
--environment "Variables={TABLE_NAME=tasks}"
The TABLE_NAME environment variable matters more than it looks. Hardcoding the table name inside the Python file instead is a common shortcut that breaks the moment you deploy a second stage or a second environment, since both would fight over the same table.
Step 5: Test and Invoke Your Function
Before wiring up API Gateway, confirm the function itself works by invoking it directly with a payload shaped like what API Gateway will eventually send.
$ aws lambda invoke \
--function-name task-manager-api \
--cli-binary-format raw-in-base64-out \
--payload '{"httpMethod":"POST","body":"{\"title\":\"Ship the API\"}"}' \
response.json
$ cat response.json
{"statusCode": 201, "headers": {"Content-Type": "application/json"}, "body": "{\"id\": \"3f2a9c4e-91b7-4c3a-8f2d-6a1e0c9b5d7f\", \"title\": \"Ship the API\", \"done\": false}"}
A 201 status code with an item back means the function, the IAM role, and the DynamoDB table (created in Step 8, so invoke this again after that step if you are following in order) are all wired correctly at the code level, before API Gateway enters the picture at all.
Step 6: Create a REST API With Amazon API Gateway
Create the REST API and a /tasks resource under its root path. API Gateway needs the root resource ID before it can attach anything else.
$ API_ID=$(aws apigateway create-rest-api --name task-manager-api --query 'id' --output text)
$ ROOT_ID=$(aws apigateway get-resources --rest-api-id $API_ID --query 'items[0].id' --output text)
$ TASKS_ID=$(aws apigateway create-resource \
--rest-api-id $API_ID \
--parent-id $ROOT_ID \
--path-part tasks \
--query 'id' --output text)
Repeat create-resource once more with --path-part {id} and --parent-id $TASKS_ID to add the /tasks/{id} path the single-item routes need.
Step 7: Connect API Gateway to Lambda With Proxy Integration
With the resources in place, attach an ANY method to each one and point it at Lambda using AWS_PROXY integration, which forwards the entire request instead of requiring a manual mapping template.
$ aws apigateway put-method \
--rest-api-id $API_ID --resource-id $TASKS_ID \
--http-method ANY --authorization-type NONE
$ aws apigateway put-integration \
--rest-api-id $API_ID \
--resource-id $TASKS_ID \
--http-method ANY \
--type AWS_PROXY \
--integration-http-method POST \
--uri arn:aws:apigateway:us-east-1:lambda:path/2015-03-31/functions/arn:aws:lambda:us-east-1:123456789012:function:task-manager-api/invocations
$ aws lambda add-permission \
--function-name task-manager-api \
--statement-id apigateway-invoke \
--action lambda:InvokeFunction \
--principal apigateway.amazonaws.com \
--source-arn "arn:aws:execute-api:us-east-1:123456789012:$API_ID/*/*"
$ aws apigateway create-deployment --rest-api-id $API_ID --stage-name prod
That last command is easy to forget and the most common reason a change appears to do nothing. API Gateway does not auto-deploy. Every route or integration change sits invisible until you run create-deployment again.
Step 8: Add a Database Layer With DynamoDB
Create the table the function already expects. On-demand billing mode means you pay per request instead of provisioning throughput you have to guess at in advance, which fits a project with unpredictable early traffic.
$ aws dynamodb create-table \
--table-name tasks \
--attribute-definitions AttributeName=id,AttributeType=S \
--key-schema AttributeName=id,KeyType=HASH \
--billing-mode PAY_PER_REQUEST
If your project needs relational joins, multi-table transactions, or SQL, DynamoDB is the wrong tool and Amazon Aurora or RDS is worth a look instead. For a flat task list keyed by ID, a single DynamoDB table with on-demand pricing is simpler to operate and cheaper at low volume.
Step 9: Secure the API With Throttling and an Authorizer
An API with no rate limiting is an open invitation for a traffic spike, accidental or otherwise, to run up your bill or exhaust DynamoDB’s on-demand scaling curve. Create a usage plan and require an API key on every route.
$ aws apigateway create-usage-plan \
--name task-manager-basic \
--throttle burstLimit=20,rateLimit=10 \
--quota limit=10000,period=MONTH
For anything beyond a personal project, swap the API-key approach for a proper Lambda authorizer or Amazon Cognito user pool authorizer, so requests carry a verifiable identity rather than a shared secret that anyone with the key can reuse.
Step 10: Deploy Everything as Code With AWS SAM
Running each CLI command by hand is fine for learning how the pieces connect, but it does not scale to a second environment or a teammate who needs to reproduce your stack. AWS SAM turns the entire setup, function, API, table, and permissions, into one template you can redeploy on demand. If you have used Terraform for AWS infrastructure before, SAM will feel familiar, just narrower in scope and purpose-built for serverless.
# template.yaml
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: Task Manager serverless API
Resources:
TasksTable:
Type: AWS::Serverless::SimpleTable
Properties:
TableName: tasks
PrimaryKey:
Name: id
Type: String
TaskManagerFunction:
Type: AWS::Serverless::Function
Properties:
Handler: lambda_function.handler
Runtime: python3.13
MemorySize: 256
Timeout: 10
Environment:
Variables:
TABLE_NAME: tasks
Policies:
- DynamoDBCrudPolicy:
TableName: tasks
Events:
TasksApi:
Type: Api
Properties:
Path: /tasks
Method: ANY
TaskByIdApi:
Type: Api
Properties:
Path: /tasks/{id}
Method: ANY
Outputs:
ApiUrl:
Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/tasks"
$ sam build
$ sam deploy --guided
sam deploy --guided walks through stack naming and confirmation prompts once, then saves the answers to samconfig.toml so every later deploy is a plain sam deploy. The DynamoDBCrudPolicy helper generates the same least-privilege permissions you wrote by hand in Step 2, scoped automatically to the table SAM creates.
Step 11: Add Logging, Metrics, and X-Ray Tracing
Every Lambda invocation already writes to CloudWatch Logs by default through the AWSLambdaBasicExecutionRole policy attached in Step 2. Turn on active tracing to see exactly where time goes across Lambda, DynamoDB, and API Gateway in a single request.
$ aws lambda update-function-configuration \
--function-name task-manager-api \
--tracing-config Mode=Active
To find failures fast once the API is live, a CloudWatch Logs Insights query beats scrolling through raw logs.
fields @timestamp, @message
| filter @message like /ERROR/
| sort @timestamp desc
| limit 20
Step 12: Cut Cold Starts and Right-Size Memory
A 256 MB Python function like this one typically cold-starts in well under a second, but if your API sits behind a mobile app or a latency-sensitive frontend, even that can be worth trimming. Provisioned concurrency keeps a set number of execution environments warm at all times.
$ aws lambda put-provisioned-concurrency-config \
--function-name task-manager-api \
--qualifier prod \
--provisioned-concurrent-executions 2
That command creates a standing cost even when nobody is calling the API, so treat it as a production decision, not a default. Memory tuning is usually the higher-leverage fix, and the pricing section below shows why: Lambda bills GB-seconds, so a function that finishes faster at a higher memory setting can end up cheaper than a slow, skinny one.
Your Complete Working Project
Once every step above is done, the project folder looks like this, and any of the three deployment paths (manual CLI, SAM, or a CI pipeline built on either) can redeploy it from scratch.
task-manager-api/
├── lambda_function.py
├── requirements.txt
├── template.yaml
├── trust-policy.json
├── dynamodb-policy.json
├── events/
│ └── create-task.json
└── README.md
Test the live, deployed API the same way an actual client would, with plain HTTPS calls against the stage URL API Gateway generated in Step 7 or the ApiUrl output SAM printed in Step 10.
$ curl -X POST https://abc123xyz.execute-api.us-east-1.amazonaws.com/prod/tasks \
-H "Content-Type: application/json" \
-d '{"title":"Write the tutorial"}'
{"id":"7f3e1a2b-9c4d-4e5f-8a1b-2c3d4e5f6a7b","title":"Write the tutorial","done":false}
$ curl https://abc123xyz.execute-api.us-east-1.amazonaws.com/prod/tasks
[{"id":"7f3e1a2b-9c4d-4e5f-8a1b-2c3d4e5f6a7b","title":"Write the tutorial","done":false}]
A 201 on the first call and your new task echoed back in the list on the second call means every layer, IAM, Lambda, API Gateway, and DynamoDB, is correctly wired end to end.
AWS Lambda, API Gateway, and DynamoDB Pricing in 2026
Serverless pricing is granular by design, and the Task Manager API from this tutorial costs close to nothing at low traffic because of how generous the combined free tiers are. The figures below come directly from AWS’s published pricing pages.
| Service | Monthly Free Tier | Pay-as-You-Go Rate |
|---|---|---|
| Lambda requests | 1,000,000 requests | $0.20 per 1 million requests |
| Lambda compute | 400,000 GB-seconds | $0.0000166667 per GB-second |
| Lambda Provisioned Concurrency | None | $0.0000041667 per GB-second, whether invoked or not |
| Lambda durable functions | Included above | $8.00 per million operations |
| API Gateway REST API | Included in AWS Free Tier for new accounts | $3.50 per million calls (first tier) |
| API Gateway HTTP API | Included in AWS Free Tier for new accounts | $1.00 per million calls (first tier) |
| DynamoDB writes (on-demand) | 25 GB storage | $0.625 per million write request units |
| DynamoDB reads (on-demand) | Included above | $0.125 per million read request units |
At 100,000 requests a month, a workload most side projects never reach, the Task Manager API stays entirely inside the free tier on Lambda and DynamoDB, and costs roughly $0.35 on API Gateway REST API calls alone once you clear the free allotment. Switching to an HTTP API instead of a REST API cuts that per-call rate by more than 70%, at the cost of the usage plans and API keys Step 9 depends on. Full details are on AWS’s Lambda pricing page, API Gateway pricing page, and DynamoDB on-demand pricing page.
Common Pitfalls When Building Serverless APIs
Most Lambda API problems trace back to one of these seven mistakes, all of them easy to make once and expensive to debug later.
- Hardcoding the table name. Reading it from an environment variable, as this tutorial does, is what lets you run a second stage without the two environments colliding.
- Attaching AdministratorAccess “just to get it working.” That shortcut in Step 2 turns into a security incident the day the function is compromised. Scope the role to exactly the actions it calls.
- Forgetting to redeploy the API Gateway stage. Route and integration changes sit invisible until you run
create-deploymentagain, covered in Step 7. - Returning a raw dictionary instead of the proxy-integration shape. Skipping
statusCode,headers, and a stringifiedbodyturns every response into a 502. - Skipping CORS headers and then debugging a phantom browser failure that is actually a preflight rejection, not a backend bug at all.
- Setting memory too low to save pennies. Lambda bills GB-seconds, so a slow function at low memory frequently costs more than a fast one at higher memory.
- Skipping throttling entirely. Without the usage plan from Step 9, a traffic spike or a retry loop in a buggy client can scale Lambda faster than DynamoDB’s on-demand mode can absorb it.
Troubleshooting AWS Lambda and API Gateway Errors
These ten errors account for nearly every support thread about a Lambda-backed API. Match the message, then apply the fix.
| Error or Symptom | Likely Cause | Fix |
|---|---|---|
| HTTP 502, “Internal server error” | Lambda returned something that isn’t valid proxy-integration JSON | Wrap every response in a helper like respond() with statusCode, headers, and a stringified body |
| HTTP 403 Forbidden | Missing API key on a route protected by a usage plan, or a resource policy mismatch | Send the key in the x-api-key header and confirm the usage plan is linked to the API stage |
| AccessDeniedException on dynamodb:PutItem | IAM role is missing that specific DynamoDB action | Add the action to the inline policy created in Step 2 |
| “Task timed out after 10.00 seconds” | Function timeout too low for a cold start plus a DynamoDB call | Raise Timeout in template.yaml, or with update-function-configuration |
| “Unable to import module ‘lambda_function'” | The zip file was created with the code inside a subfolder | Rezip from inside the project directory so lambda_function.py sits at the archive root |
| “No ‘Access-Control-Allow-Origin’ header present” | CORS isn’t enabled on the API Gateway resource | Enable CORS on the resource and redeploy the stage |
| Code changes don’t appear when calling the API | Forgot to redeploy the API Gateway stage after a route change | Run create-deployment again. Lambda code updates are instant, but stages are not |
| ProvisionedThroughputExceededException | A traffic burst outran DynamoDB’s on-demand scaling curve | Confirm the table is in PAY_PER_REQUEST mode. For extreme bursts, warm the table with light traffic first |
| Function passes in the console test but fails through the live API | Console test events don’t match the shape API Gateway actually sends | Test with a saved event file that mirrors a real API Gateway payload, like events/create-task.json |
| {“message”:”Missing Authentication Token”} | Requested a path, method, or stage that doesn’t exist on the deployment | Double-check the exact route and stage prefix in the URL, including /prod |
Advanced Tips for Production-Ready Lambda APIs
The steps above get a working API deployed. Getting one ready for real traffic takes a few more decisions.
Provisioned Concurrency, SnapStart, and Managed Instances
Three different AWS features solve cold starts, and they are not interchangeable. Provisioned concurrency, used in Step 12, keeps a fixed number of environments warm at $0.0000041667 per GB-second whether or not they are invoked. SnapStart snapshots an already-initialized environment and restores from it, and now extends beyond its original Java-only scope to Python and .NET, cutting cold starts without a standing cost. Lambda Managed Instances, new in 2026, target a different problem entirely: steady, memory-heavy workloads that need up to 32 GB, well past the standard 10,240 MB ceiling, plus scheduled scaling and automatic tag propagation for fleets of functions.
For the Task Manager API in this tutorial, none of the three are necessary at launch. Add provisioned concurrency only after real traffic data shows cold starts are actually a user-facing problem, not a theoretical one.
A few other decisions separate a demo from something you would put in front of paying users. Adopt AWS Lambda Powertools for structured logging, tracing, and custom metrics instead of scattered print statements. Use a Lambda alias with weighted routing to shift a small percentage of traffic to a new version before a full rollout, so a bad deploy affects a fraction of requests instead of all of them. If your dependencies push the 50 MB zipped deployment package limit, package the function as a container image instead, which supports up to 10 GB. And if you later add an endpoint that runs AI-generated or user-submitted code, look at Lambda MicroVMs, the isolated, stateful execution environment AWS introduced in 2026 specifically for that case, with sessions that persist for up to 8 hours.
AWS Lambda vs Azure Functions vs Cloudflare Workers vs Cloud Run
Lambda is not the only way to run this pattern. The table below compares what each platform actually publishes for its free tier and pricing structure, useful if you are choosing a platform rather than assuming AWS by default.
| Platform | Monthly Free Tier | Paid Rate |
|---|---|---|
| AWS Lambda | 1,000,000 requests + 400,000 GB-seconds | $0.20 per 1M requests + $0.0000166667 per GB-second |
| Azure Functions (Consumption) | 1,000,000 requests + 400,000 GB-seconds | Pay-as-you-go, rate varies by region |
| Cloudflare Workers | 100,000 requests/day (Free plan) | Paid plan from $5/month: 10M requests + 30M CPU-ms included, then $0.30 per additional million requests |
| Google Cloud Run | 2,000,000 requests + 180,000 vCPU-seconds + 360,000 GiB-seconds | Pay-as-you-go, rate varies by region |
The free-tier math is closer than most people expect. Azure’s Consumption plan publishes the same 1 million requests and 400,000 GB-seconds Lambda offers, almost figure for figure. Cloudflare Workers runs on V8 isolates rather than full execution environments, which avoids traditional cold starts but caps free CPU time at 10 milliseconds per invocation. Cloud Run bills by container instance rather than per function, which suits a service with several routes better than five separate deployables. For a deeper head-to-head on the two most-compared options, see Cloudflare Workers vs Lambda.
Frequently Asked Questions
What is the difference between AWS Lambda and a traditional server?
A traditional server runs continuously whether or not it is handling requests, and you pay for that uptime. An AWS Lambda function only runs while it is processing an event, then AWS shuts the execution environment down. You pay per request and per GB-second of actual compute, not for idle capacity, which is why a low-traffic API like the one in this tutorial can run almost entirely inside the free tier.
How much does a small AWS Lambda API cost per month?
For the Task Manager API built in this tutorial, likely nothing at low volume. AWS’s free tier covers 1 million Lambda requests, 400,000 GB-seconds of compute, and 25 GB of DynamoDB storage every month. The first cost most small projects hit is API Gateway’s $3.50 per million REST API calls once free-tier request volume is used up, according to AWS’s published pricing.
What programming languages does AWS Lambda support in 2026?
Lambda’s managed runtimes include Python (up to 3.14), Node.js (up to 24.x), Java, Go, Ruby, and .NET, which added .NET 10 support as both a managed runtime and a container base image in January 2026, per AWS’s own weekly roundup. Any language that can run in a container also works through Lambda’s container-image deployment option.
What is the maximum execution time for a Lambda function?
15 minutes per invocation. If a workload regularly needs longer than that, it is a signal to break the job into smaller Lambda invocations, hand it to Step Functions for orchestration, or move it to a container-based service like Fargate instead of forcing it into Lambda’s execution model.
Do I need API Gateway to use Lambda?
No. Lambda function URLs give a function its own HTTPS endpoint without API Gateway at all, and services like S3 or EventBridge can invoke Lambda directly with no API layer whatsoever. API Gateway earns its place when you need usage plans, API keys, request validation, or multiple routes managed under one stage, all of which the Task Manager API in this tutorial uses.
Is DynamoDB required, or can I use RDS instead?
DynamoDB is not mandatory, just the simplest fit for a flat, key-value task list. Lambda can also connect to Amazon Aurora or RDS through the RDS Data API or a connection pooler like RDS Proxy, worth considering if your data model needs relational joins, foreign keys, or SQL that DynamoDB’s item-based model does not support well.
How do I stop my AWS Lambda function from timing out on cold starts?
First confirm the timeout in Step 4 is generous enough to cover a cold start plus a DynamoDB round trip, usually a few seconds is plenty for a function this size. If cold starts are still a measurable problem under real traffic, provisioned concurrency or SnapStart, both covered in the advanced tips section, address it directly, at the cost of a standing charge or a more involved setup.
How do I secure a Lambda-backed API in production?
Layer it: a least-privilege IAM role as built in Step 2, a usage plan with throttling as built in Step 9, and a real authorizer, either a custom Lambda authorizer or an Amazon Cognito user pool, in place of a shared API key for anything beyond a personal project. Add AWS WAF in front of API Gateway if the API is public-facing and expects meaningful traffic.
Related Coverage
- Cloudflare Workers vs Lambda 2026: 240x Cold Start Gap
- How to Set Up Terraform for AWS: 13 Steps, 75 Min [2026]
- How to Set Up Amazon EKS: 12 Steps, 90 Min [2026]
- Step Functions vs Airflow: $0 vs $357/mo Floor [2026]
- ECS vs EKS vs Fargate: $0 vs $73/mo Control Plane [2026]
- Amazon Aurora vs RDS: 5x Faster, 21% Pricier [2026]
- AWS FinOps Agent Launches Free in $16.5B Market [2026]


