Lambda-to-Lambda Calls vs. SNS Chaining in AWS: When and How to Use Each
Modern serverless architectures often require connecting multiple AWS Lambda functions. Two common patterns are direct Lambda-to-Lambda invocation and chaining via Amazon SNS. This post explains when to use each, with diagrams, CloudFormation templates, and TypeScript code for both approaches.
When to Use Each Pattern
Choosing between direct Lambda-to-Lambda calls and SNS chaining depends on your workflow’s requirements for coupling, reliability, and scalability. While it is technically possible to invoke one Lambda function from another, it is important to understand the implications of doing so synchronously. Synchronous Lambda-to-Lambda calls—where the first function waits for a response from the second—are generally discouraged as a best practice. This is because they can lead to increased latency, higher costs, and more complex error handling, especially if the downstream Lambda experiences throttling or failures. In most cases, tightly coupled, synchronous workflows are better implemented using other AWS services such as Step Functions, which are designed for orchestrating distributed processes with built-in error handling and state management.
However, making an asynchronous Lambda-to-Lambda call is a valid and supported pattern. In this approach, the first Lambda invokes the second using the “Event” invocation type, which does not wait for a response and allows both functions to scale independently. Asynchronous invocation is not considered an anti-pattern and can be useful for fire-and-forget scenarios where the result is not immediately needed by the caller.
SNS chaining, on the other hand, is better suited for loosely coupled, event-driven, or fan-out scenarios. By introducing SNS as an intermediary, you can decouple the producer (the first Lambda) from one or more consumers (downstream Lambdas). This pattern is ideal when you want to enable retries, support multiple subscribers, or build more resilient and scalable systems. SNS allows each consumer to process messages independently, making it easier to evolve your architecture over time.
1. Direct Lambda-to-Lambda Calls
This approach lets one Lambda invoke another directly using the AWS SDK. While asynchronous invocation is possible and sometimes useful, synchronous Lambda-to-Lambda calls should generally be avoided in favor of more robust orchestration solutions.
Lambda-to-Lambda Architecture
Lambda-to-Lambda CloudFormation Template (AWS SAM)
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Globals:
Function:
Runtime: nodejs18.x
Timeout: 10
Architectures:
- x86_64
Environment:
Variables:
AWS_NODEJS_CONNECTION_REUSE_ENABLED: '1'
Resources:
LambdaA:
Type: AWS::Serverless::Function
Properties:
FunctionName: LambdaA
Handler: index.handler
CodeUri: ./lambda-a/
Policies:
- Statement:
- Effect: Allow
Action: lambda:InvokeFunction
Resource: !GetAtt LambdaB.Arn
Environment:
Variables:
LAMBDA_B_ARN: !GetAtt LambdaB.Arn
LambdaB:
Type: AWS::Serverless::Function
Properties:
FunctionName: LambdaB
Handler: index.handler
CodeUri: ./lambda-b/
Lambda-to-Lambda TypeScript Example (AWS SDK v3)
// LambdaA: Invoking LambdaB directly
import { LambdaClient, InvokeCommand } from "@aws-sdk/client-lambda";
const lambda = new LambdaClient({ region: process.env.AWS_REGION });
export const handler = async () => {
const command = new InvokeCommand({
FunctionName: process.env.LAMBDA_B_ARN!,
Payload: Buffer.from(JSON.stringify({ key: "value" })),
InvocationType: "Event", // "Event" for async
});
const response = await lambda.send(command);
const payload = response.Payload ? Buffer.from(response.Payload).toString() : undefined;
return { statusCode: 200, body: payload };
};
2. Chaining with SNS
This pattern uses SNS to decouple Lambda functions. LambdaA publishes a message to SNS; LambdaB and other subscribers are subscribed to the topic and triggered asynchronously.
SNS Chaining Architecture
SNS Chaining CloudFormation Template (AWS SAM)
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Globals:
Function:
Runtime: nodejs18.x
Timeout: 10
Architectures:
- x86_64
Environment:
Variables:
AWS_NODEJS_CONNECTION_REUSE_ENABLED: '1'
Resources:
LambdaA:
Type: AWS::Serverless::Function
Properties:
FunctionName: LambdaA
Handler: index.handler
CodeUri: ./lambda-a/
Policies:
- Statement:
- Effect: Allow
Action: sns:Publish
Resource: !Ref Topic
Environment:
Variables:
SNS_TOPIC_ARN: !Ref Topic
LambdaB:
Type: AWS::Serverless::Function
Properties:
FunctionName: LambdaB
Handler: index.handler
CodeUri: ./lambda-b/
Events:
SnsEvent:
Type: SNS
Properties:
Topic: !Ref Topic
LambdaC:
Type: AWS::Serverless::Function
Properties:
FunctionName: LambdaC
Handler: index.handler
CodeUri: ./lambda-c/
Events:
SnsEvent:
Type: SNS
Properties:
Topic: !Ref Topic
Queue:
Type: AWS::SQS::Queue
QueueSubscription:
Type: AWS::SNS::Subscription
Properties:
TopicArn: !Ref Topic
Protocol: sqs
Endpoint: !GetAtt Queue.Arn
LambdaD:
Type: AWS::Serverless::Function
Properties:
FunctionName: LambdaD
Handler: index.handler
CodeUri: ./lambda-d/
Events:
SQSTrigger:
Type: SQS
Properties:
Queue: !GetAtt Queue.Arn
Topic:
Type: AWS::SNS::Topic
SNS Chaining TypeScript Example (AWS SDK v3)
// LambdaA: Publish to SNS
import { SNSClient, PublishCommand } from "@aws-sdk/client-sns";
const sns = new SNSClient({ region: process.env.AWS_REGION });
export const handler = async () => {
const command = new PublishCommand({
TopicArn: process.env.SNS_TOPIC_ARN!,
Message: JSON.stringify({ key: "value" }),
});
await sns.send(command);
return { statusCode: 200, body: "Message sent" };
};
Conclusion
To summarize, synchronous Lambda-to-Lambda calls—where one function waits for a response from another—are considered an anti-pattern and should be avoided. This approach can introduce unnecessary latency, increase costs, and complicate error handling. If you need to coordinate multiple Lambda functions in a workflow, consider using AWS Step Functions or event-driven patterns instead.
If you must trigger another Lambda from your function, prefer asynchronous invocation. Asynchronous Lambda-to-Lambda calls are not an anti-pattern and can be useful for fire-and-forget scenarios where the result is not needed immediately by the caller.
SNS chaining is the recommended approach for decoupling, fan-out, and building resilient, scalable serverless systems. By using SNS, you can easily support multiple independent subscribers, enable retries, and evolve your architecture as requirements change. Choose the pattern that best fits your application’s needs, but avoid synchronous Lambda-to-Lambda calls in production systems.
Comments