Skip to content
AWS aws serverless 5 min read

Lambda Handlers & Runtimes

When you deploy code to AWS Lambda (Amazon’s serverless compute service that runs your code without you managing servers), AWS needs to know exactly which function in your code to call when something triggers it. That single entry point is called the handler. The handler receives two pieces of information — the event (the data that triggered the function) and the context (metadata about the running invocation). Getting the handler and runtime right is the difference between a function that runs smoothly and one that fails with a confusing error before your code ever executes.

What the handler is

The handler is the method Lambda invokes when your function runs. You configure it as a string in the format file.function. For example, index.handler means “call the function named handler exported from the file index.”

Lambda calls your handler once per invocation and passes it two arguments:

  • event — a JSON object containing the input data. Its shape depends on what triggered the function. An Amazon S3 (Simple Storage Service) upload produces a different event than an Amazon API Gateway request.
  • context — an object with runtime information: the request ID, the remaining execution time, the function name, the memory limit, and more.

Here is a complete Node.js handler:

// index.js
exports.handler = async (event, context) => {
  console.log("Request ID:", context.awsRequestId);
  console.log("Time remaining (ms):", context.getRemainingTimeInMillis());

  const name = event.name || "world";

  return {
    statusCode: 200,
    body: JSON.stringify({ message: `Hello, ${name}!` }),
  };
};

For this code, the handler config string must be exactly index.handler — the filename without .js, a dot, then the exported function name.

Gotcha: If your handler string does not match file.function precisely, Lambda fails before running a single line of your code with a cryptic error like Runtime.HandlerNotFound: index.handler is undefined or not exported. If you rename the file to app.js or the export to main, update the handler config to match (app.main). This trips up almost everyone at least once.

Cold starts and where to put init code

Anything outside the handler function runs once per cold start — the first time a new execution environment spins up. Once warm, that environment is reused for later invocations and the init code is skipped. This is the single most important performance lever in Lambda.

Put expensive setup — database connections, AWS SDK clients, configuration loading — outside the handler so it is created once and reused across many warm invocations:

// index.js
const { DynamoDBClient } = require("@aws-sdk/client-dynamodb");

// Runs ONCE per cold start, reused on warm invocations.
const ddb = new DynamoDBClient({ region: "us-east-1" });

exports.handler = async (event) => {
  // Runs on EVERY invocation. Reuses the client above.
  const result = await ddb.send(/* your command */);
  return result;
};

If you create the client inside the handler, you pay the setup cost on every single invocation — slower and more expensive at scale.

Tip: A reused database connection survives across invocations, but the same environment also handles only one request at a time. Do not store per-request state in module-level variables, or one user’s data can leak into another’s response.

Supported runtimes

A runtime is the language environment Lambda uses to run your code. AWS provides managed runtimes, keeps them patched, and retires old versions on a schedule.

Runtime familyExample identifiers (2026)Notes
Node.jsnodejs22.x, nodejs20.xFast cold starts, great for web/API workloads.
Pythonpython3.13, python3.12Popular for data, automation, and APIs.
Javajava21, java17Slower cold starts; SnapStart helps a lot.
.NETdotnet8Compiled, strong performance.
Rubyruby3.4Good for scripting and small services.
Gouse provided.al2023Go ships as a compiled binary on a custom runtime.

When to use a managed runtime (and when not)

Use a managed runtime for the vast majority of work — you write code, AWS handles the language environment, patching, and security updates. Reach for a custom runtime only when your language is not on the supported list, or you need a specific language version AWS does not offer.

Custom runtimes

A custom runtime lets you run any language by implementing the Lambda Runtime API (a small HTTP loop that polls for events and posts back responses). You package it on the Amazon Linux base, identified as provided.al2023. This is how Rust, Go, or an unsupported language version runs on Lambda. Most teams never need this, but it is the escape hatch when you do.

Container image packaging

Instead of a .zip of your code, you can package a Lambda as a container image (a Docker image) up to 10 GB. This is the right choice when your dependencies are large (machine learning libraries, for example) or your team already standardizes on containers and shares base images.

# Build and push a container image, then create the function from it
docker build -t my-lambda .
aws ecr create-repository --repository-name my-lambda
docker tag my-lambda:latest 111122223333.dkr.ecr.us-east-1.amazonaws.com/my-lambda:latest
docker push 111122223333.dkr.ecr.us-east-1.amazonaws.com/my-lambda:latest

aws lambda create-function \
  --function-name my-container-fn \
  --package-type Image \
  --code ImageUri=111122223333.dkr.ecr.us-east-1.amazonaws.com/my-lambda:latest \
  --role arn:aws:iam::111122223333:role/my-lambda-role

Output:

{
    "FunctionName": "my-container-fn",
    "FunctionArn": "arn:aws:lambda:us-east-1:111122223333:function:my-container-fn",
    "PackageType": "Image",
    "State": "Pending",
    "LastUpdateStatus": "InProgress"
}

Cost note: Zip and container Lambdas are billed identically — per request plus GB-seconds of compute. Container images stored in Amazon ECR (Elastic Container Registry) cost about $0.10 per GB per month, a tiny add-on for most teams.

Setting the handler and runtime

Console steps

  1. Open the AWS Management Console and go to the Lambda service.
  2. Select your function, then open the Code tab.
  3. Scroll to Runtime settings and choose Edit.
  4. Pick a Runtime (for example, nodejs22.x) and set Handler to index.handler.
  5. Choose Save.

AWS CLI

aws lambda update-function-configuration \
  --function-name my-function \
  --runtime nodejs22.x \
  --handler index.handler

Output:

{
    "FunctionName": "my-function",
    "Runtime": "nodejs22.x",
    "Handler": "index.handler",
    "LastUpdateStatus": "Successful"
}

Best Practices

  • Keep the handler thin: parse the event, call your business logic, return a response. Put logic in separate, testable modules.
  • Initialize SDK clients and database connections outside the handler so they are reused across warm invocations.
  • Match the handler config string to your actual file.function exactly to avoid HandlerNotFound.
  • Pin a specific runtime version and upgrade deliberately before AWS deprecates it.
  • Use container images only when dependencies are large or you already standardize on Docker; otherwise prefer the smaller, faster zip package.
  • Read context.getRemainingTimeInMillis() for long-running work so you can fail gracefully before the timeout.
  • Never store per-request data in module-level variables — that scope is shared across invocations in the same environment.
Last updated June 15, 2026
Was this helpful?