Best Practices for AWS SDK v3 Client Initialization in Lambda Handler Functions

Initialize AWS SDK v3 clients at the module level outside your Lambda handler to reuse connections across warm invocations, and defer any asynchronous setup to lazy initialization inside the handler to avoid cold-start timeouts.

The aws/agent-toolkit-for-aws repository provides authoritative guidance on optimizing AWS Lambda performance through proper SDK client management. Understanding where and when to initialize your clients can significantly reduce latency and prevent connection throttling in serverless applications.

Why Handler-Level Initialization Hurts Performance

When AWS Lambda launches your function, it creates an execution environment that persists across multiple invocations. Creating a new SDK client inside the handler on every request forces the runtime to establish fresh network connections and perform request signing repeatedly. This pattern increases latency, consumes additional memory, and can trigger throttling when many concurrent invocations occur.

According to the reference documentation in skills/core-skills/aws-sdk-js-v3-usage/references/lambda.md, the SDK v3 clients are designed specifically to support static initialization patterns that maximize reuse of the underlying execution environment.

Static Module-Level Clients (Safe by Default)

AWS SDK v3 clients contain no asynchronous startup work, making them safe to instantiate once at the top level of your module. The client object remains available to every handler call without incurring initialization costs on subsequent invocations.

As documented in skills/core-skills/aws-sdk-js-v3-usage/references/lambda.md:

SDK clients themselves (no async setup) are safe to initialize outside the handler.

import { S3Client, GetObjectCommand } from '@aws-sdk/client-s3';

// Safe to create once at module load time
const s3 = new S3Client({});

export const handler = async (event: any) => {
  const cmd = new GetObjectCommand({ Bucket: 'my-bucket', Key: 'file.txt' });
  const response = await s3.send(cmd);
  return { statusCode: 200, body: 'OK' };
};

Lazy Initialization for Asynchronous Dependencies

When your client requires configuration from external sources—such as secrets, parameter store values, or database connections—you must avoid top-level await operations. Performing async work at import time risks freezing during Lambda's pre-flight check, leading to timeouts before the handler even executes.

The recommended pattern defers expensive initialization to the first invocation while caching the result in a module-scoped variable:

import { S3Client, GetObjectCommand } from '@aws-sdk/client-s3';
import { SecretsManagerClient, GetSecretValueCommand } from '@aws-sdk/client-secrets-manager';

let s3: S3Client | undefined;
let ready = false;

async function init() {
  // Fetch configuration asynchronously
  const sm = new SecretsManagerClient({});
  const secret = await sm.send(new GetSecretValueCommand({ SecretId: 'my-config' }));
  const config = JSON.parse(secret.SecretString ?? '{}');
  
  // Create client with fetched configuration
  s3 = new S3Client({ endpoint: config.endpoint });
  ready = true;
}

export const handler = async (event: any) => {
  if (!ready) await init(); // First invocation performs async work
  const cmd = new GetObjectCommand({ Bucket: 'my-bucket', Key: 'file.txt' });
  const response = await s3!.send(cmd);
  return { statusCode: 200, body: 'OK' };
};

This pattern appears in skills/core-skills/aws-sdk-js-v3-usage/references/lambda.md under the "Lazy init inside handler" section, ensuring your Lambda starts quickly while still benefiting from connection reuse after initialization.

Managing SDK Versions with Lambda Layers

Lambda runtimes ship with a specific AWS SDK version that may not match your requirements. To control the exact SDK version and ensure consistent behavior across environments, either bundle the SDK with your deployment package or publish it as a Lambda layer.

Create a layer with pinned dependencies:

// layer/package.json
{
  "dependencies": {
    "@aws-sdk/client-s3": "<=3.750.0",
    "@aws-sdk/client-dynamodb": "<=3.750.0"
  }
}

After running npm install and zipping the nodejs/node_modules directory, publish the layer and reference it in your function configuration. This approach is documented in skills/core-skills/aws-sdk-js-v3-usage/references/lambda.md under the "Creating a Lambda Layer" section.

Key Reference Files in the Agent Toolkit

The following files provide complete implementation guidance for Lambda optimization:

Summary

  • Initialize clients at module level — AWS SDK v3 clients have no async startup work, making them safe to create outside the handler for connection reuse across warm invocations.
  • Defer async setup to first invocation — Fetch secrets and configuration inside the handler on first call, storing results in module-level variables to prevent pre-flight timeouts.
  • Pin SDK versions with layers — Bundle specific SDK versions or use Lambda layers to avoid dependency conflicts with the runtime's built-in SDK.

Frequently Asked Questions

Can I create AWS SDK clients inside the Lambda handler?

While technically possible, creating clients inside the handler forces new connection establishment on every invocation, increasing latency and memory consumption. The aws/agent-toolkit-for-aws repository recommends static module-level initialization unless you specifically require lazy loading for async configuration.

Why is async initialization at the top level dangerous?

Top-level asynchronous operations can freeze during Lambda's initialization phase, causing your function to timeout before processing any events. As noted in skills/core-skills/aws-sdk-js-v3-usage/references/lambda.md, this "One-Time Async Initialization" risk is mitigated by moving async work into the handler and guarding it with a readiness flag.

How do I handle configuration that requires secrets?

Use the lazy initialization pattern: declare your client variable at module scope, create an async initialization function that fetches secrets and constructs the client, and call this function conditionally inside your handler. Store the initialized client and a readiness flag in module-level variables to ensure subsequent invocations reuse the configured instance.

Should I use the built-in SDK or bundle my own?

Lambda runtimes include AWS SDK v3, but the version may not match your feature requirements. For production workloads, bundle specific SDK versions or use Lambda layers to ensure deterministic behavior, as documented in the Agent Toolkit's Lambda reference files.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →