Lambda & Serverless
AWS Lambda runs your code in response to events without you provisioning or managing any servers. You upload a function, AWS executes it on demand, scales it automatically from zero to thousands of concurrent invocations, and bills you only for the compute time you actually use. This is serverless: no instances to patch, no capacity to plan, no idle cost.
The serverless concept
“Serverless” doesn’t mean there are no servers - it means you never think about them. AWS owns provisioning, scaling, patching, and availability. You own only your function code and its configuration. The unit of deployment shrinks from a server to a single function, and the operational surface area collapses.
Serverless is a spectrum, not just Lambda. S3, DynamoDB, SQS, API Gateway, and EventBridge are all serverless building blocks that compose into entire applications with no servers to manage.
The event-driven model
Lambda functions are invoked by events. A function sits idle (and free) until something triggers it: an HTTP request, a file landing in S3, a message on a queue, a scheduled timer, a database change. This event-driven design makes Lambda a natural glue layer between AWS services.
Invocation comes in three flavors:
- Synchronous — caller waits for the response (e.g. API Gateway, ALB).
- Asynchronous — event is queued and the caller returns immediately; Lambda retries on failure (e.g. S3, SNS).
- Stream/poll-based — Lambda polls a source and invokes in batches (e.g. Kinesis, SQS, DynamoDB Streams).
Handlers
The handler is the function AWS calls per event. It receives an event (the trigger payload) and a context (runtime metadata), and returns a response.
// index.mjs - Node.js 20 handler
export const handler = async (event, context) => {
console.log("Request ID:", context.awsRequestId);
const name = event.queryStringParameters?.name ?? "world";
return {
statusCode: 200,
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ message: `Hello, ${name}!` }),
};
};
Deploy and invoke from the CLI:
aws lambda invoke \
--function-name hello-devcraftly \
--payload '{"queryStringParameters":{"name":"DevCraftly"}}' \
--cli-binary-format raw-in-base64-out \
response.json
Output:
{
"StatusCode": 200,
"ExecutedVersion": "$LATEST"
}
Triggers
Triggers connect event sources to your function. Common ones include:
| Trigger | Use case |
|---|---|
| API Gateway / Function URL | HTTP/REST APIs and webhooks |
| S3 | Process objects on upload (thumbnails, ETL) |
| EventBridge / CloudWatch Events | Scheduled jobs (cron) and event routing |
| SQS / SNS | Decoupled async processing, fan-out |
| DynamoDB Streams / Kinesis | React to data changes in real time |
Cold starts
When Lambda needs a fresh execution environment - first invocation, after scaling out, or after idle reclaim - it must initialize the runtime and your code before handling the request. This added latency is a cold start.
- Lightweight runtimes (Node.js, Python) start fastest; heavy JVM/.NET cold-start slower.
- Keep deployment packages small and move heavy init outside the handler (it runs once per environment, then is reused on “warm” invocations).
- Use Provisioned Concurrency to keep environments pre-warmed for latency-sensitive endpoints (at extra cost).
Cold starts mostly hurt low-traffic or spiky synchronous APIs. High-traffic functions stay warm naturally. Don’t pay for Provisioned Concurrency unless you’ve measured a real latency problem.
Pricing
Lambda billing has two components:
- Requests — per invocation (with a generous always-free tier of 1M/month).
- Duration — GB-seconds: memory allocated x execution time, billed per millisecond.
Memory and CPU scale together - more memory means proportionally more CPU, so a function can sometimes finish faster and cheaper at a higher memory setting. Profile to find the sweet spot.
When to use Lambda vs EC2/containers
| Choose | When |
|---|---|
| Lambda | Event-driven, spiky, short tasks (<15 min); want zero ops and scale-to-zero |
| Containers (ECS/Fargate) | Long-running services, custom runtimes, steady traffic, large images |
| EC2 | Full OS control, specialized hardware (GPU), or sustained high utilization |
Lambda caps at a 15-minute timeout and limited ephemeral storage. For long-running, stateful, or constantly busy workloads, a container or EC2 is usually simpler and cheaper than fighting these limits.
Best Practices
- Keep functions small and single-purpose; compose them with EventBridge and queues.
- Grant each function a least-privilege execution role; never share one broad role.
- Initialize SDK clients and DB connections outside the handler to reuse them across warm invocations.
- Set sensible timeouts, memory, and concurrency limits; add a dead-letter queue for async failures.
- Use structured logging to CloudWatch and tracing with X-Ray for observability.
- Deploy with Infrastructure as Code (SAM, CDK, or Serverless Framework) for repeatable, versioned releases.