Lambda makes the first 80% easy. The last 20% is where most teams lose time: cold starts at p99, predictable costs at scale, and security that survives an audit. Here is the playbook we use on every engagement.
1. Right-size memory before you touch anything else
In Lambda, CPU is allocated proportionally to memory. Doubling memory often halves execution time and reduces total cost. We profile every production function with AWS Lambda Power Tuning, a step-function that benchmarks a range of memory settings and finds the sweet spot between latency and cost.
Teams that ship with the default 128 MB are almost always leaving both performance and money on the table. Set a baseline around 512 MB for most API handlers, then tune downward if the function is genuinely CPU-light.
2. Defeat cold starts strategically, not everywhere
Cold starts matter for synchronous, user-facing paths. They rarely matter for async workers or scheduled jobs. Spend your budget where users feel it.
- Provisioned Concurrency: reserves warmed instances. Use it for login, checkout, and anything hit under 1s p99 SLOs.
- SnapStart (Java, Python, .NET): snapshots an initialized runtime. 10× faster cold starts, with zero ongoing cost on Java (Python and .NET bill for snapshot cache storage and restores).
- Runtime choice: Node and Go cold-start in ~100 ms. Java and .NET without SnapStart are 1–3 s.
- Bundle size: a bloated bundle slows cold starts, and module initialization dominates that cost. Tree-shake. Exclude the AWS SDK if the runtime ships with it.
3. Event-driven, not cron-driven
The cheapest and most reliable Lambda pipelines are event-driven. Instead of polling on a schedule, wire Lambda directly to the event that should trigger the work: S3 uploads, DynamoDB streams, SQS messages, EventBridge rules.
A Lambda that runs only when something happens is easier to reason about, cheaper to operate, and naturally handles bursty traffic.
4. Structured logs, tracing, and metrics from day one
You cannot retrofit observability after an incident. Three things must be in place before a function hits production:
- Structured JSON logs with a correlation ID per invocation. Use Powertools for Lambda; it handles this by default.
- AWS X-Ray or ADOT traces covering every downstream call: DynamoDB, external APIs, SQS.
- Custom metrics via EMF (Embedded Metric Format) for business KPIs, not just technical ones.
// With AWS Lambda Powertools (TypeScript)
import { Logger } from '@aws-lambda-powertools/logger';
import { Tracer } from '@aws-lambda-powertools/tracer';
import { Metrics, MetricUnit } from '@aws-lambda-powertools/metrics';
const logger = new Logger();
const tracer = new Tracer();
const metrics = new Metrics();
export const handler = async (event) => {
logger.info('Order received', { orderId: event.orderId });
metrics.addMetric('OrderProcessed', MetricUnit.Count, 1);
// ... business logic ...
};
5. Security: least privilege, really
The most common Lambda security finding in the audits we run: an execution role with s3:* on *. Fix this with:
- One IAM role per function. Don't share a generic "LambdaFullAccess" role.
- Resource-scoped permissions: name the exact ARN of the queue, bucket, or table.
- Secrets via AWS Secrets Manager or Parameter Store with KMS, never environment variables for credentials.
- Enable AWS Lambda code signing for production functions.
Would your Lambda architecture survive an audit? Describe your system: a one-page assessment within 48 hours.
Get your assessment →6. Control cost before it controls you
Lambda's pay-per-use pricing is a trap when functions are poorly architected. Budget guardrails we set on every project:
- Reserved concurrency: caps runaway fan-out. Keeps a misconfigured loop from generating a five-figure bill.
- Timeouts sized to actual work, not the 15-minute maximum.
- Cost anomaly detection alerts on unusual spend per function.
- Compute Savings Plans: cover steady-state Lambda usage at up to 17% off.
7. Design for idempotency
Lambda guarantees at-least-once delivery, not exactly-once. Any function that writes state must be idempotent, otherwise duplicate deliveries corrupt your data. Use Powertools' idempotency utility with DynamoDB as the lock store, or a message ID in your own table.
The short list
- Tune memory with Power Tuning, always.
- Use Provisioned Concurrency or SnapStart only on latency-critical paths.
- Go event-driven where you can.
- Ship observability with the first function, not the hundredth.
- One role per function, resource-scoped.
- Reserve concurrency and watch cost anomalies.
- Make every writer idempotent.