13 min read
AWS Lambda in practice: where it earns its keep, where it bites back, and how to keep your code portable
AWS Lambda is an excellent fit for small, event-driven workloads, and a poor fit for heavy file processing or workloads that need long-running memory. This article describes one pipeline where Lambda performed well in production handling Twitter/X webhooks, one project where it became the wrong tool, and the architectural pattern we use to keep our business logic portable across runtimes.
TL;DR
- AWS Lambda is the right runtime for short, stateless, event-driven jobs, such as a Twitter/X webhook pipeline forwarding events into internal aggregation tools.
- Lambda becomes a poor fit when the workload pushes against its memory ceiling, its fifteen-minute execution limit, or the regional account concurrency quota, all of which we have hit during client implementations.
- Keep business logic in plain typed functions that do not depend on the Lambda runtime, and treat the handler as a thin wrapper, so that a later migration becomes a wrapper change rather than a rewrite.
Corsair Media Group
Why we use Lambda, with the caveats up front
AWS Lambda has a specific place in our backend toolkit. The service rewards small, event-driven jobs where each invocation finishes quickly and does not depend on heavy local resources. When the workload matches that shape, Lambda is a productivity win and the operational overhead is close to zero. When the workload does not, Lambda becomes the source of the next problem you have to solve.
This article walks through one project where Lambda performed well in production, a second project where Lambda was the wrong tool, the wrapper-based pattern we use to keep our business logic portable, and the tradeoffs, alternatives, and decision checks we apply on every new build.
A success story: a lightweight event pipeline from X (formerly Twitter) to backend aggregation
One of our recent backend builds is a small event pipeline that takes data from X (formerly Twitter) and forwards it into a set of internal aggregation tools. The Lambda receives a webhook from X, validates the payload, normalizes a handful of fields, and publishes the result to a downstream queue that the aggregation tools consume on their own schedule.
Each event arrives as a webhook, gets normalized inside a small Lambda function, and then fans out to a queue that the aggregation tools consume on their own schedule.
- Volume: roughly 1.8 million events per month
- Payload: averaging around 3 KB per event
- Runtime: 128 MB memory, p95 execution under 200 ms
- Cost: single-digit dollars per month on Lambda
The workload fits Lambda almost perfectly. Each event is short, stateless, and independent, so cold starts add almost nothing to perceived latency. The downstream queue handles its own batching and rate management, so the Lambda never has to reason about backpressure. AWS scales the function automatically with traffic, billing reflects only the time the code spent running, and a comparable service running full-time on a small VM would cost more while sitting idle most of the day.
A failure story: large file processing under memory pressure
A different project gave us a clearer view of where Lambda stops being a good fit. We built a system that processed large user-uploaded files, ran several transformation steps on each one, and produced an output artifact for the client's customers to download.
The inputs ranged from 50 MB to roughly 2 GB, with occasional outliers above that. We started at 3 GB of memory and worked our way up to the Lambda ceiling of 10,240 MB, and the largest files still produced out-of-memory errors. Execution times pushed past five minutes on the slow transformations, with several jobs timing out near the fifteen-minute hard cap. We spent meaningful engineering effort splitting the work into chunks just to fit it back inside the Lambda envelope.
The shape of the problem did not match the runtime. We eventually rebuilt the heavy portion of the pipeline on AWS Fargate, where a single container sized for the worst case could load the full file, transform it in place, and write the artifact in a single pass. The chunking machinery went away with it. The lesson is direct: if your workload has any meaningful chance of large inputs or long-running transformations, then Lambda is not the right home for the heavy step.
The thin-wrapper pattern: treat Lambda as a shell around portable business logic
The protection that matters most across all of these tradeoffs is a clean separation between the Lambda's ingress and egress on one side and the business logic on the other. The handler reads the event, calls a pure function that does the real work, and returns the result. The pure function knows nothing about Lambda, nothing about AWS, and nothing about the event format. It takes typed input and returns typed output.
The outer ring is the part that ties you to AWS. The inner box is the part you want to keep, and the part you can carry to a different runtime when the tradeoffs change.
- Handler: parses the AWS event, calls the inner module, serializes the response
- Business logic: pure function in a sibling module, typed input and output, runtime agnostic
- Migration cost: mostly a new wrapper for the new runtime, with the inner module untouched
In practice, the split looks like this in our codebases:
// handler.ts (thin wrapper, AWS-specific)
import type { SQSEvent } from "aws-lambda";
import { processWebhook } from "./logic";
export async function handler(event: SQSEvent) {
const input = parseEvent(event);
return processWebhook(input);
}// logic.ts (runtime-agnostic business logic)
export function processWebhook(input: WebhookInput): Result {
// validation, normalization, side-effect orchestration
}With that separation in place, migrating off Lambda becomes mostly a wrapper change. The handler that read an SQS payload becomes a handler that reads an HTTP request, a CLI argument, or a Kafka message. The business logic, where most of the engineering effort and most of the testing investment lives, moves to the new runtime without modification.
The pattern pays for itself even while you stay on Lambda. Pure functions are easy to unit test, because the test does not need to simulate an AWS event or stand up a mock runtime. The handler then becomes a small integration seam covered by a handful of targeted tests, rather than a tangle of business rules and AWS plumbing fighting each other in the test suite.
The tradeoffs that come with serverless on AWS
Lambda's convenience comes with a small set of tradeoffs we hit most often: infrastructure coupling to AWS, the regional account concurrency quota, and the per-function memory and execution-time ceilings.
Lambda ties your code to the AWS ecosystem
Every Lambda function sits inside a web of AWS services. IAM holds the access policies, CloudWatch holds the logs, triggers come from EventBridge, SQS, S3, or API Gateway, and deployment usually runs through CloudFormation or one of its descendants. The same lock-in concerns we wrote about in our article on software vendor lock-in apply here: deployment tooling, IAM policies, observability, and event infrastructure all become AWS-specific, and a migration is rarely just a code move.
The account-level concurrency limit is real, and we have hit it
AWS enforces concurrency at the account and region level, not per function. The default quota starts at 1,000 concurrent executions per region. We have, to our surprise, hit our raised ceiling of around 4,000 invocations during client implementations. That ceiling throttled new invocations until the existing ones finished, and the throttling was observable to end users during a traffic spike. Before deploying Lambda for spiky workloads, verify your headroom and request quota increases ahead of launch.
Memory and execution-time ceilings bound what you can run
The 10,240 MB memory cap and the fifteen-minute execution limit are deliberate constraints of the runtime. Multi-gigabyte in-memory transformations, video transcoding, and long streaming model invocations will eventually exceed those limits. Recognize the constraint up front, and choose a different runtime for the heavy step.
Practical alternatives for workloads that do not fit Lambda
Lambda is one option in a wider toolkit. The alternatives below are the ones we reach for most often. None are objectively better than Lambda; they are better for different jobs.
Directional summary from our own production work. Measure your specific workload before treating any of this as a guarantee.
| Workload shape | Fit | Where we reach |
|---|---|---|
| Webhook handlers and event normalizers | Strong | AWS Lambda |
| Periodic small batch jobs and cron-style work | Strong | AWS Lambda |
| Public API endpoints with steady throughput | Workable | Lambda with provisioned concurrency, or a container |
| Long-running transformations on large files | Poor | Fargate, ECS, or AWS Batch |
| Sustained high-throughput services | Poor | Managed VM running a queue consumer |
Containers on Fargate, ECS, or Kubernetes
Containers are the right answer for workloads that need more memory, longer execution times, or warm in-memory state across requests. The container can be sized for the worst case rather than the average case, and it does not restart between invocations, so cold starts disappear from the latency budget.
AWS Batch and Step Functions for orchestrated batch jobs
When the workload really is a batch job, AWS Batch handles the queueing, retries, and lifecycle of the underlying compute. Step Functions can orchestrate multi-step workflows where each step runs on the runtime that fits it best, often with Lambda used for the lightweight steps and containers used for the heavy ones.
A small managed VM running a queue consumer
For sustained throughput, a managed VM with a long-running queue consumer is often cheaper and easier to reason about than the equivalent Lambda deployment. The consumer holds persistent database connections and in-memory caches that Lambda invocations cannot reuse efficiently across calls.
Cloudflare Workers for edge-latency workloads
Cloudflare Workers is a different shape of serverless runtime that fits workloads where latency to a global audience matters more than tight AWS integration. The tradeoff is that you commit to the Cloudflare ecosystem instead of the AWS one, with the same set of portability questions to answer.
If your team already runs container orchestration, you may not need Lambda at all
Most of Lambda's appeal is the operational work it removes. AWS provisions the compute, manages the scaling, and bills you only for the time the function ran. That benefit is real when your team does not already have a platform for running event-driven workloads in production.
If your team already operates a production container platform, whether ECS, Fargate, Kubernetes, or a self-managed cluster, then much of Lambda's operational advantage has already been realized. The platform team has already paid the cost of running production infrastructure, and adding Lambda on top does not remove that cost. It introduces a second runtime with its own concurrency quotas, deployment tooling, and observability story, while the original platform stays in place alongside it.
In that situation, deploying onto the existing container platform is often operationally simpler than introducing Lambda as a second runtime. The code itself still benefits from the wrapper pattern described earlier, because keeping the business logic portable costs almost nothing once it becomes habit. The production deployment, however, should run where the operational expertise already lives.
How we decide whether Lambda is the right runtime
Before committing a workload to Lambda, we run through a short list of questions. These are not the only questions worth asking, but they catch the cases where Lambda would be a poor fit before any code is written.
- How large is the worst-case input, and does the processing fit inside 10,240 MB of memory and fifteen minutes of execution time?
- How spiky is the traffic, and does the projected peak concurrency fit inside our current account quota with a comfortable margin?
- Does the workload need warm in-memory state, persistent connections, or local caches that are expensive to rebuild on every invocation?
- Is the rest of the system already deeply integrated with AWS, or is this the first AWS-specific component in the stack?
- Can the business logic be written as a pure function with typed input and typed output, so that the Lambda handler stays thin?
Closing thoughts
Lambda is the right tool for a narrow set of jobs and the wrong tool for the rest. Matching the workload to the runtime is half the work. Writing the code so the match is not permanent is the other half.
If your team is deciding between Lambda, containers, or queue workers for a new system, then we can help evaluate the tradeoffs before the architecture hardens. Reach out through our contact page with a short description of the workload, and we will work through it with you.
Want a backend that uses Lambda where it helps and stays portable everywhere else?
Talk with CorsairContinued reading
Keep exploring related topics that connect strategy, implementation, and long-term maintenance.
Software vendor lock-in: why AI platforms make an already expensive problem harder to escape
Vendor lock-in has existed across enterprise software for decades, and AI deals add data, model, and orchestration dependencies that are harder to untangle than a typical SaaS migration. This article covers how to evaluate and protect your options before you sign, with AI as the clearest current example of a pattern that applies to every significant software vendor relationship.
Why custom software projects are harder than what you see before you sign
Most teams discover uncertainty, ownership gaps, weak requirements, hasty estimates, and production surprises only after the work has started. This article explains why that happens, what we will not do on live client calls, how we prefer to plan in cycles, and why we keep all of our staff in the United States.
Building marketing sites for performance in 2026
We start with clear structure and fast delivery for readers, then add search and analytics on top. Human review still matters when much of the open web is filling up with similar pages and overbuilt copy.