AWS Certified Developer - Associate (DVA-C02) #2 Domain 1-1 Development with AWS Services — Lambda Deep Dive
In #1 Exam Introduction, I said DVA-C02’s center of gravity is serverless, and the center of that is Lambda. The development domain is the largest at 32%, and Lambda is the service that appears most often within it. The exam treats Lambda not as “the place you upload a function” but as a behavior problem: “which event invokes the function, how, where does it go on failure, and how is concurrent execution controlled?”
This post moves quickly past Lambda’s basic concepts (runtimes, handlers) and spends time on the points that decide answers on the exam — invocation types, concurrency, and failure handling.
The three invocation types #
The most important frame for understanding Lambda is who invokes it, and how. Because retry behavior and failure handling change completely with the invocation type, the exam asks about this distinction repeatedly.
| Invocation type | Representative trigger | Retries | Failure handling |
|---|---|---|---|
| Synchronous | API Gateway, ALB, direct Invoke | None (the caller handles it) | Returns the error to the caller |
| Asynchronous | S3, SNS, EventBridge | Automatic 2 retries (3 total) | DLQ or Destination |
| Stream polling (poll-based) | SQS, Kinesis, DynamoDB Streams | Repeats until expiry/success | Batch failure handling, DLQ |
Synchronous invocation #
The caller waits for a response. A typical case is API Gateway invoking Lambda and returning the result to the client. If Lambda throws an error, it does not retry and passes it straight back to the caller. Whether to retry is decided by the caller (e.g., API Gateway, the SDK).
Asynchronous invocation #
The caller puts the event into Lambda’s internal queue and gets an immediate return. This is when S3 calls Lambda with an object-created event, or SNS delivers a message. If the function fails, Lambda automatically retries two more times (with delay), and if it still fails, it sends the event to a DLQ (Dead Letter Queue) or a Destination.
Stream polling (event source mapping) #
SQS, Kinesis, and DynamoDB Streams don’t invoke Lambda directly. Instead, the Lambda service polls the source via an event source mapping, pulls a batch, and then invokes the function. An SQS standard queue is polled automatically, and a message that fails processing returns to the queue after the visibility timeout; once it exceeds maxReceiveCount, it moves to the DLQ.
Exam trap: the DLQ for SQS is a setting on the queue itself. The DLQ for asynchronous invocation is a setting on the Lambda function. The two live in different places.
Concurrency #
Concurrency is the number of function instances running at the same moment. Each account/region has a default concurrent execution limit (usually 1000), shared across functions. The exam often asks about the two concurrency-control options.
| Option | Purpose | Behavior |
|---|---|---|
| Reserved Concurrency | Reserve/limit concurrency for a specific function | Carves out a portion that only that function can use. Protects it from other functions’ bursts, and at the same time becomes that function’s upper bound |
| Provisioned Concurrency | Eliminate cold starts | Keeps pre-initialized execution environments warm. For latency-sensitive workloads |
The key distinction is this: reserved concurrency is “guarantee/limit a quantity,” and provisioned concurrency is “warm it up in advance.” The answer to “the first response is slow due to a cold start” is not reserved concurrency but provisioned concurrency.
When a function exceeds its concurrency limit, Lambda throttles additional invocations. On synchronous invocation, a 429 TooManyRequestsException is returned to the caller; on asynchronous invocation, it goes into the retry queue and is tried again later.
Cold starts #
When a new execution environment is created for the first time, the extra latency for downloading the code and initializing the runtime is the cold start. The ways to reduce it are well defined.
- Provisioned concurrency — pre-initialize environments and keep them warm.
- Reduce package size, initialize outside the handler — create DB connections and SDK clients outside the handler once, and reuse them.
- Minimize VPC connections — once a bottleneck, but greatly improved with the introduction of Hyperplane ENIs. Still, avoid unnecessary VPC connections.
Objects initialized outside the handler are kept while the same execution environment is reused (warm). Code that opens a new DB connection inside the handler every time is an anti-pattern.
Environment variables and layers #
- Environment variables — inject configuration values, separated from the code. They’re encrypted at rest by default, and for sensitive values you encrypt them with a KMS customer managed key or read them at runtime from Parameter Store / Secrets Manager. An answer that puts a plaintext password into an environment variable is wrong.
- Layers — separate common libraries and dependencies so multiple functions can share them. They keep the deployment package small and let you manage dependencies in one place. You can attach up to 5 per function, and they count toward the total 250 MB unzipped limit.
Versions and aliases #
When you publish code, Lambda creates an immutable version, and an alias points to a specific version. Aliases become the core of weighted canary deployment in deployment strategies, so here we’ll just hold onto the idea that “an alias is a pointer to a version.”
Failure handling — DLQ and Destination #
On asynchronous invocation, when all retries fail, the event must be preserved so it isn’t lost.
| Method | Success/failure distinction | Target |
|---|---|---|
| DLQ | Failure only | SQS queue or SNS topic |
| Lambda Destination | Both success and failure | SQS, SNS, EventBridge, another Lambda |
Destinations are the newer and recommended approach. They can route both success and failure, and the failure event carries richer error context. A DLQ only preserves failure events.
Idempotency #
Asynchronous and stream-based invocations can deliver the same event more than once (at-least-once). SQS standard queues and asynchronous retries can all create duplicates, so the function must be designed so that running multiple times with the same input produces the same result.
- Record an idempotency key (an order ID, etc.) in DynamoDB with a conditional write to prevent duplicate processing.
- The more side-effecting the operation (payments, inventory decrements), the more essential idempotent design becomes.
This topic is covered further in #6 SDK Development Patterns.
Exam question patterns #
- “If a Lambda invoked by API Gateway fails, does it retry automatically?” → It’s synchronous, so Lambda doesn’t retry automatically. The caller handles it.
- “A Lambda invoked by an S3 event failed. How do you avoid losing the event?” → It’s asynchronous, so configure a DLQ/Destination.
- “The function’s first response is slow (cold start). What’s the fix?” → Provisioned concurrency.
- “One function eats up the whole account’s concurrency, throttling others.” → Isolate it with reserved concurrency.
- “It opens a new DB connection on every invocation.” → Initialize outside the handler and reuse.
- “SQS delivered the same message twice.” → Idempotent design (standard queues are at-least-once).
Common traps #
1) Applying asynchronous retry counts to synchronous #
The automatic 2 retries are the behavior of asynchronous invocation. Synchronous invocation does not retry within Lambda.
2) Confusing reserved and provisioned concurrency #
Reserved is reserve/limit a quantity; provisioned is eliminate cold starts. If it’s “latency-sensitive,” it’s provisioned.
3) Confusing DLQ location #
The DLQ for an SQS trigger is a queue setting; the DLQ for asynchronous Lambda is a function setting.
4) Storing secrets in plaintext in environment variables #
Read sensitive data from Secrets Manager / Parameter Store, or encrypt it with KMS.
Wrap-up #
What this post locked in:
- Invocation types — synchronous (no retries), asynchronous (automatic 2 retries + DLQ/Destination), stream polling (event source mapping)
- Concurrency — reserved (reserve/limit a quantity) and provisioned (eliminate cold starts) serve different purposes
- Cold starts — mitigate with provisioned concurrency and reuse via initialization outside the handler
- Environment variables separate config; layers share common dependencies
- Failure handling — Destinations (both success and failure) are recommended over DLQs (failure only)
- Because delivery is at-least-once, idempotent design is essential
Next — Domain 1-2 API Gateway #
If Lambda is the backend logic, API Gateway is the gateway in front of it that receives and routes HTTP requests. In #3 API Gateway, I’ll cover the difference between REST APIs and HTTP APIs, Lambda proxy integration, authentication (IAM, Cognito, Lambda authorizers), throttling and usage plans, caching, and stages and deployments.