Start with the event, not the function
Lambda is most useful when work begins because something happened: an HTTP request arrived, an object landed in storage, a message appeared on a queue, or a scheduled task became due. That event-first view makes the boundary of the function clearer.
Before writing code, define the trigger, the expected event shape, the response or side effect, and what should happen when a downstream system is slow or unavailable. This prevents a Lambda function from becoming a small, unobservable monolith.
A simple decision framework
- Use Lambda for short-lived, independently deployable event handlers and automation.
- Use a queue between producers and consumers when traffic can spike or a downstream dependency needs protection.
- Keep synchronous request paths small; move long-running work to asynchronous processing.
- Choose a container or long-running service when you need persistent connections, predictable warm capacity, or work that outgrows a function boundary.
Keep the handler thin
The handler should translate the incoming event into an application call, validate only what it owns, and return a clear result. Business rules belong in small modules that can be tested without the Lambda runtime.
// handler.ts
import { processOrder } from "./orders";
export const handler = async (event: { orderId?: string }) => {
if (!event.orderId) return { statusCode: 400, body: "orderId is required" };
await processOrder(event.orderId);
return { statusCode: 202, body: "accepted" };
};Design for retries and duplicates
Many event sources retry delivery. A function can therefore receive the same event more than once, and a successful-looking invocation can still leave an incomplete downstream side effect. Idempotency matters for notifications, provisioning, payments, and record updates.
A useful pattern is to store a stable event identifier before performing the irreversible action. If the identifier already exists, return safely rather than doing the work twice.
Give the function only the access it needs
Permissions are part of the application design. Start with a narrowly scoped execution role: one bucket prefix, one queue, one table or one secret—not broad account-level access. Separate environments so development access cannot affect production resources.
Configuration belongs in environment settings or a configuration service. Secrets belong in a secrets manager, never source control or logs.
Make operating signals deliberate
- Log structured context: request or event ID, operation name, outcome, and safe timing data.
- Publish useful metrics: successes, failures, retry count, queue age, and business-level completion where appropriate.
- Set alarms on sustained failures and backlog growth, not every transient exception.
- Trace calls across API, Lambda, queue, and downstream services when debugging a production path.
A deployment checklist
A reliable serverless deployment is more than uploading a zip file. The release should package dependencies consistently, run unit tests, validate infrastructure changes, and deploy with explicit environment configuration. For important workflows, route a small portion of traffic first and watch errors, duration, and business signals before widening the release.
The goal is not to use Lambda everywhere. The goal is to give a small event-driven responsibility the right operational boundary: clear input, least privilege, retries that are safe, and enough visibility to improve it over time.