How to Build and Deploy Your First AWS Lambda Function

Blog / Amazon Web Services · April 25, 2024 · Updated June 10, 2026 · 11 min read
How to Build and Deploy Your First AWS Lambda Function

AWS Lambda lets you run code without provisioning or managing servers: you upload a function, choose a runtime, attach a trigger, and AWS runs it on demand, scaling automatically and billing only for the compute you actually use. The fastest correct path to a working Lambda in 2026 is to pick a current runtime (Python 3.13 or Node.js 22), write a small handler(event, context) function, attach a least-privilege IAM execution role, wire up a trigger such as API Gateway or an S3 event, and deploy with infrastructure-as-code (AWS SAM or Terraform) so the whole thing is repeatable.

This is a hands-on getting-started guide aimed at developers and technical decision-makers. We will build and deploy a real function, set up its permissions safely, and cover the trigger, packaging, testing, and cost basics you need. For production hardening, see our companion post on AWS Lambda best practices. MicroPyramid has shipped serverless and AWS workloads for 12+ years across 50+ projects, and the steps below reflect how we start a new function today.

What AWS Lambda is and when to use it

Lambda is AWS's event-driven, serverless compute service. Your code sits idle (and costs nothing) until an event arrives, at which point AWS spins up an isolated execution environment, runs your function, and tears it down. You are charged per request and for the milliseconds of execution time at the memory you allocate, so there is no idle server to pay for.

Lambda is a strong fit when:

  • Work is event-driven or spiky — file uploads, queue messages, webhooks, scheduled jobs, or HTTP APIs with uneven traffic.
  • Each unit of work is short-lived (Lambda's hard limit is a 15-minute timeout) and largely stateless.
  • You want to avoid managing servers, patching, and capacity planning and let scaling happen automatically.

Reach for something else when: you need long-running or always-on processes, very low and predictable latency without cold starts, heavy in-memory state, or fine-grained control over the OS. The table further down compares Lambda with EC2 and Fargate for those cases.

Core concepts you need first

A handful of terms show up everywhere in Lambda. Get these straight and the rest of the service is easy to reason about.

  • Handler — the function AWS invokes for each event. Its signature is def handler(event, context): in Python or exports.handler = async (event, context) => {} in Node.js. You tell Lambda which file and function to call (e.g. app.handler).
  • Event — the JSON payload describing what happened, shaped by the trigger (an API Gateway request, an S3 ObjectCreated record, an EventBridge schedule, etc.).
  • Context — runtime metadata: the request ID, remaining execution time, function name, and memory limit.
  • Cold start — the extra latency on the first invocation in a new execution environment, when AWS has to download your code and initialise the runtime. Warm invocations reuse the environment and skip this.
  • Timeout — the maximum time a single invocation may run, from 1 second up to 15 minutes (default 3 seconds — almost always too low).
  • Memory — you allocate 128 MB to 10,240 MB; CPU scales with memory, so more memory often means a faster and cheaper function.
  • Execution role — the IAM role Lambda assumes to call other AWS services on your behalf. This is where least-privilege matters most.

Create your first function

The fastest way to see Lambda work is the console, but the repeatable way is infrastructure-as-code (covered below). Start with the console to build intuition, then graduate to IaC for anything real.

In the AWS Console

  1. Sign in to AWS using an IAM Identity Center user, not the root account (more on this below). Open the Lambda console.
  2. Choose Create function, then Author from scratch.
  3. Give it a name, pick a current runtime (Python 3.13 or Node.js 22), and choose arm64 (Graviton) for the architecture — it is typically cheaper and faster.
  4. Under Permissions, let Lambda create a basic execution role to start, then replace it with a scoped role (next section) before the function touches any other service.
  5. Paste your handler into the inline editor, Deploy, then use Test with a sample event.

A real Python 3.13 handler

Here is a handler that returns a proper API Gateway proxy response — the shape you need when Lambda sits behind HTTP. Note the client is created outside the handler so warm invocations reuse it.

# app.py  --  Python 3.13 Lambda handler for an API Gateway (proxy) integration
import json
import logging

logger = logging.getLogger()
logger.setLevel(logging.INFO)

# Initialise clients/config ONCE per execution environment (outside the handler).
# Warm invocations reuse this, which keeps cold-start cost down.
# import boto3
# dynamodb = boto3.resource("dynamodb")


def handler(event, context):
    """Entry point AWS Lambda invokes for each request."""
    logger.info("request_id=%s path=%s", context.aws_request_id, event.get("rawPath"))

    name = (event.get("queryStringParameters") or {}).get("name", "world")

    return {
        "statusCode": 200,
        "headers": {"Content-Type": "application/json"},
        "body": json.dumps({"message": f"Hello, {name}!"}),
    }

The execution role: least-privilege IAM

This is the part old tutorials get dangerously wrong. Never use root account access keys, and never hand a function a wildcard policy like "Action": "*" on "Resource": "*". A function should be able to do exactly what it needs and nothing more.

Do this instead:

  • Human access to AWS — for you and your team — goes through IAM Identity Center (formerly AWS SSO) with short-lived credentials and MFA. Treat the root user as break-glass only: enable MFA on it, create no access keys for it, and do not use it day to day.
  • Each Lambda gets its own execution role, scoped to the specific actions and resources it uses. Start from CloudWatch Logs permissions (so the function can log) and add only what the code calls.

The policy below lets a function write logs and read/write a single DynamoDB table — note the explicit actions and the table-specific ARN, not a wildcard.

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "WriteOwnLogs",
      "Effect": "Allow",
      "Action": [
        "logs:CreateLogGroup",
        "logs:CreateLogStream",
        "logs:PutLogEvents"
      ],
      "Resource": "arn:aws:logs:us-east-1:111122223333:log-group:/aws/lambda/my-function:*"
    },
    {
      "Sid": "AccessOneTable",
      "Effect": "Allow",
      "Action": [
        "dynamodb:GetItem",
        "dynamodb:PutItem",
        "dynamodb:Query"
      ],
      "Resource": "arn:aws:dynamodb:us-east-1:111122223333:table/Orders"
    }
  ]
}

For a deeper walkthrough of roles, trust policies, and the difference between identity- and resource-based policies, see our guide to AWS IAM roles and policies. When you outgrow hand-written policies, AWS-managed policies and IAM Access Analyzer can help you generate least-privilege policies from observed activity.

Triggers: how your function gets invoked

A Lambda does nothing until an event source invokes it. The three you will reach for most:

  • API Gateway (or a Lambda Function URL) — exposes the function over HTTPS for REST/HTTP APIs and webhooks. Use API Gateway when you need auth, throttling, custom domains, or request validation; a Function URL is the simplest option for a single endpoint.
  • S3 — invoke on object events such as s3:ObjectCreated:*, e.g. to process an uploaded image or ingest a data file. See our walkthrough of Lambda with S3 and DynamoDB for a complete pipeline.
  • EventBridge Scheduler — run on a cron or rate schedule for batch jobs, clean-ups, and reports. This is the modern replacement for the older "CloudWatch Events" scheduled rules.

Other common sources include SQS, SNS, DynamoDB Streams, and Kinesis for stream and queue processing.

Packaging and deployment options

You have three ways to package a function, plus infrastructure-as-code to deploy them repeatably.

Option Best for Size limit Notes
Zip archive Most functions; small/medium dependencies 50 MB zipped (250 MB unzipped) Simplest; built by SAM/Terraform automatically.
Lambda layer Sharing common dependencies across functions Counts toward the 250 MB unzipped limit Keeps each function package small; up to 5 layers per function.
Container image Large dependencies, custom runtimes, ML models Up to 10 GB Build with Docker, push to ECR; better when zip limits bite.

For deployment, do not click around the console for production. Define everything as code:

  • AWS SAM — a serverless-focused extension of CloudFormation; concise, great for pure Lambda + API Gateway + DynamoDB stacks.
  • Terraform — provider-agnostic IaC if you already standardise on it across your infrastructure.

A minimal SAM template for our function looks like this.

# template.yaml  --  AWS SAM
AWSTemplateFormatVersion: "2010-09-09"
Transform: AWS::Serverless-2016-10-31

Resources:
  HelloFunction:
    Type: AWS::Serverless::Function
    Properties:
      Handler: app.handler
      Runtime: python3.13
      Architectures: [arm64]
      MemorySize: 256
      Timeout: 10
      Policies:
        # Scoped managed-policy fragment -> generates a least-privilege role.
        - DynamoDBCrudPolicy:
            TableName: Orders
      Events:
        Api:
          Type: HttpApi
          Properties:
            Path: /hello
            Method: get
# Build and deploy with the AWS SAM CLI
sam build
sam deploy --guided      # first time: walks you through stack name, region, etc.

# Or update a simple zip with the AWS CLI (function + role must already exist)
zip function.zip app.py
aws lambda update-function-code \
  --function-name my-function \
  --zip-file fileb://function.zip

Testing and viewing logs

  • Local testing. Invoke handlers as plain functions in unit tests with a sample event dict — no AWS needed. sam local invoke and sam local start-api run the function in a Docker container that mimics the Lambda environment for end-to-end checks.
  • Logs. Anything you log (and any uncaught error) goes to Amazon CloudWatch Logs, grouped under /aws/lambda/<function-name>. Emit structured JSON logs so you can query them with CloudWatch Logs Insights.
  • Tracing. Enable AWS X-Ray to see where time goes across services for a single request — invaluable once a function calls several other services.

A quick note on cost and performance

Lambda's pricing model is pay-per-use: you are billed per request plus compute (GB-seconds), with a perpetual free tier of 1M requests and 400,000 GB-seconds per month. There is no charge while a function is idle, which is what makes it economical for spiky workloads. Two levers move the needle most:

  • Architecture — running on arm64 (Graviton) is typically cheaper per millisecond than x86 and often faster.
  • Right-sized memory — because CPU scales with memory, the cheapest setting is rarely the lowest. Use AWS Lambda Power Tuning to find the sweet spot.

If you are weighing a serverless rewrite as part of a wider move to AWS, our cloud migration services and AWS consulting services cover the architecture and cost trade-offs end to end.

Lambda vs EC2 vs Fargate

Lambda is not always the right compute choice. Here is how it compares for a typical workload.

AWS Lambda AWS Fargate Amazon EC2
Model Function, event-driven Containers, serverless Virtual machines
Scaling Automatic, to zero Automatic (task-based) Manual / Auto Scaling groups
Max run time 15 minutes Unbounded Unbounded
You manage Just code Containers OS, patching, capacity
Best for Spiky, short, event-driven work Long-running containers without server ops Full control, steady high load, special hardware
Idle cost None Per running task Per running instance

A common pattern is to mix them: Lambda for glue, webhooks, and scheduled jobs; Fargate or EC2 for steady, long-running services.

Common pitfalls to avoid

  • Default 3-second timeout. Set a realistic timeout; the default trips up almost every first function.
  • Creating clients inside the handler. Initialise SDK clients and connections in the global scope so warm invocations reuse them.
  • Over-broad IAM. Wildcard actions/resources are a security liability — scope every execution role.
  • Secrets in plaintext environment variables. Pull secrets from AWS Secrets Manager or SSM Parameter Store at runtime.
  • Ignoring cold starts on latency-sensitive paths. Keep packages small and consider provisioned concurrency where it matters.
  • No idempotency or dead-letter queue. Event sources can retry; design handlers to be safe to re-run.

These last few are where production maturity lives. Our AWS Lambda best practices post goes deep on cold starts, idempotency, secrets handling, and observability for production-grade functions.

Frequently Asked Questions

What is AWS Lambda used for?

AWS Lambda runs code in response to events without you managing servers. Typical uses are HTTP APIs and webhooks (behind API Gateway), processing files when they land in S3, running scheduled jobs with EventBridge, reacting to queue and stream messages (SQS, Kinesis, DynamoDB Streams), and gluing AWS services together. It scales automatically and bills only for the compute you use, which suits spiky and event-driven workloads.

What runtimes does AWS Lambda support in 2026?

Use current, supported runtimes: Python 3.13 (and 3.12) and Node.js 22 (and 20) are the safe defaults, alongside current Java, .NET, Go, and Ruby runtimes. Avoid Python 3.9 and 3.10, which are deprecated — functions on deprecated runtimes stop receiving security patches and eventually cannot be updated. Choose the arm64 (Graviton) architecture for better price-performance.

How do I give a Lambda function permissions safely?

Give each function its own IAM execution role scoped with least privilege: list the specific actions it needs and target specific resource ARNs rather than using "Action": "*" or "Resource": "*". Never use root account access keys. For human access to AWS, use IAM Identity Center with short-lived credentials and MFA, and reserve the root user for break-glass only. IAM Access Analyzer can generate tight policies from observed activity.

How do I reduce Lambda cold starts?

Initialise SDK clients, connections, and config in the global scope so warm invocations reuse them; keep the deployment package small by pruning dependencies and using layers; right-size memory (more memory means more CPU, often a faster start); and choose arm64. For latency-critical paths, provisioned concurrency or SnapStart keeps environments warm. Our best-practices guide covers this in depth.

Lambda vs EC2 — when should I use which?

Use Lambda for short (under 15 minutes), event-driven, or spiky work where you want automatic scaling to zero and no server management. Use EC2 (or Fargate for containers) for long-running or always-on services, steady high load, large in-memory state, special hardware, or when you need full OS control. Many systems mix them: Lambda for glue and webhooks, EC2/Fargate for steady core services.

Should I deploy Lambda from the console or with infrastructure-as-code?

The console is great for learning and quick experiments, but use infrastructure-as-code — AWS SAM or Terraform — for anything real. IaC makes deployments repeatable, reviewable, and version-controlled, sets up the function, role, and triggers together, and lets you promote the same definition across dev, staging, and production. Clicking through the console by hand does not scale and is hard to audit.

Share this article