Part of my AWS learning journey — transitioning from Systems Engineer to Cloud/DevOps. This session moves from managed databases into serverless compute — a fundamentally different execution model that changes how you think about infrastructure entirely.
📋 Topics Covered
| # | Topic | Type |
|---|---|---|
| 1 | What is Serverless — The Mental Model | Concept |
| 2 | Lambda vs EC2 — When to Use What | Concept + Interview |
| 3 | Key Components of Lambda | Concept |
| 4 | Lambda Handler — Entry Point | Concept + Lab |
| 5 | Firecracker — What Actually Runs Lambda | Concept |
| 6 | Cold Start vs Warm Invocation | Concept + Interview |
| 7 | The Lambda Init Phase | Concept + DevOps |
| 8 | Provisioned Concurrency — Eliminating Cold Starts | Concept + Cert |
| 9 | Best Practices for Production Lambda | DevOps |
| 10 | Lambda Triggers | Concept + Lab |
| 11 | Invocation Models — Synchronous, Asynchronous, Poll-Based | Concept + Interview |
| 12 | Execution Roles and Permissions | Concept + Lab |
| 13 | Lambda in a VPC — Private Resource Access | Concept + Interview |
| 14 | Lambda Pricing | Concept |
| 15 | Lab — Lambda Triggered by S3 | Lab |
| 16 | Interview Questions | Interview |
| 17 | Assignment | Practice |
What is Serverless — The Mental Model
"Serverless" doesn't mean there are no servers. It means you don't see, manage, or think about servers — AWS runs them invisibly on your behalf.
With traditional compute (EC2), you provision a server, choose its size, install the OS, deploy your code, and pay for that server 24/7 whether it's handling traffic or sitting idle. Serverless inverts all of that — you write a function, upload it, and AWS runs it only when something triggers it. The infrastructure appears and disappears automatically.
The core shift: In EC2, you manage servers that run your code. In Lambda, you write code and AWS manages everything that runs it.
Three defining traits of serverless:
- No server provisioning or management — AWS handles all infrastructure
- Automatic scaling — from zero to thousands of concurrent executions, instantly
- Pay only for what you use — billed per request + per millisecond of execution time (when idle, you pay nothing)
Lambda vs EC2 — When to Use What
The Chef Analogy
EC2 = Hire a full-time chef. The chef is at the restaurant all day, every day — whether three customers arrive or three hundred. You pay the chef's salary regardless of how many orders come in.
Lambda = Call a chef only when an order arrives. No orders → no cost. An order comes in → the chef cooks → leaves. You pay only for the cooking time.
Side-by-Side Comparison
| EC2 | Lambda | |
|---|---|---|
| Server management | You manage (patching, scaling, monitoring) | AWS manages everything |
| Startup model | Always running | Runs only on trigger |
| Max runtime | Indefinite | 15 minutes per invocation |
| Scaling | Manual or ASG | Automatic, instant |
| Billing | Per hour (even when idle) | Per request + per GB-second of execution |
| Use for | Long-running apps, stateful services, databases | Event-driven, short-lived tasks, automation |
| State | Can maintain state on disk | Stateless — no persistent state between invocations |
When Lambda is the right choice
- Responding to S3 file uploads (resize an image, parse a CSV)
- Processing messages from SQS, SNS, or Kinesis
- API backends triggered by API Gateway
- Scheduled tasks (run a report every night at midnight via EventBridge)
- Real-time stream processing (DynamoDB Streams, Kinesis)
- Glue code between services in an event-driven pipeline
When Lambda is NOT the right choice
- Long-running processes (more than 15 minutes — use EC2 or Fargate)
- Applications that hold state in memory across requests
- Workloads requiring persistent local disk (use EC2 with EBS)
- High-performance compute (HPC, ML training — use GPU EC2)
🎯 Interview Q: Can Lambda run forever? → No. Maximum execution timeout is 15 minutes. If your task takes longer, it must be broken into smaller steps (Step Functions) or moved to EC2/Fargate.
Key Components of Lambda
| Component | What it is |
|---|---|
| Function | Your code — packaged and deployed to Lambda |
| Runtime | The language environment (Python 3.12, Node.js 20, Java 21, Go, etc.) |
| Handler | The specific function inside your code that Lambda calls on each invocation |
| Layers | Reusable packages (libraries, dependencies) shared across multiple functions |
| Execution Role | An IAM Role that grants Lambda permission to call other AWS services |
| Trigger | The event source that invokes the function (S3, API Gateway, SQS, etc.) |
| Log Streams | CloudWatch Log Streams where function output and errors are captured |
| Function Settings | Memory (128 MB to 10 GB), timeout (up to 15 min), environment variables, concurrency limits |
Lambda Handler — The Entry Point
The handler is the entry point to your Lambda function — the specific function in your code that AWS calls when the function is triggered. You define it in the Lambda console as filename.function_name.
Example in Python:
def lambda_handler(event, context):
print("Event received:", event)
return {
"statusCode": 200,
"body": "Hello from Lambda!"
}
The two parameters every handler receives:
event: A dictionary/object containing all the information about what triggered this invocation — for an S3 trigger, it contains the bucket name and object key; for API Gateway, it contains the HTTP method, path, and request body; for SQS, it contains the message contents.
context: An object with metadata about the invocation itself — the function name, the remaining execution time, the request ID, the CloudWatch log stream name.
The return value of the handler becomes the response — for a synchronous invocation like API Gateway, the response is returned to the caller. For asynchronous invocations, the return value is ignored.
Firecracker — What Actually Runs Lambda
When you invoke a Lambda function, something needs to execute your code in an isolated, secure environment. AWS built Firecracker specifically for this.
Firecracker is AWS's lightweight microVM (micro virtual machine) technology — it provides the security and isolation of a full virtual machine with the speed and low resource usage needed for serverless workloads.
Think of it this way: a traditional VM takes seconds to start because it boots a full OS. A Docker container starts faster but shares the host kernel (less isolation). Firecracker hits the middle — it's a minimal VM with its own kernel that starts in milliseconds, uses very little memory, and is fully isolated from all other Lambda functions running on the same physical server.
Why Firecracker matters:
- Lambda functions from different customers can run on the same physical hardware with full isolation between them — security without sacrificing density
- Cold starts happen in milliseconds instead of seconds because Firecracker microVMs boot extremely fast
- The same technology powers AWS Fargate (serverless containers)
Cold Start vs Warm Invocation
This is one of the most important Lambda concepts for both interviews and production use.
Cold Start
A cold start happens when Lambda needs to create a brand new execution environment to run your function — because no existing one is available (the function hasn't been invoked recently, or concurrency is ramping up faster than existing environments can handle).
What happens during a cold start:
AWS allocates a Firecracker microVM → loads the Lambda runtime (Python, Node, etc.) → downloads and unpacks your function code → runs all code outside the handler (the Init Phase) → then finally runs the handler.
This entire process adds latency — typically 100ms to a few seconds depending on the runtime, package size, and how much initialization code runs.
Warm Invocation
A warm invocation happens when Lambda reuses an existing execution environment that's already fully initialized from a previous invocation.
AWS already has the Firecracker microVM running, the runtime loaded, and your code in memory → it directly invokes the handler → skips the entire Init Phase.
This is significantly faster — no initialization overhead, just the handler execution time.
The Lifecycle Visualized
First invocation (cold):
Create microVM → Load runtime → Download code → Run Init Phase (code outside handler) → Run handler → Return response → Environment stays warm for a whileSecond invocation (warm):
Reuse existing environment → Run handler directly → Return responseAfter extended idle period:
AWS recycles the environment → Next invocation is cold again🎯 Interview Q: After how much idle time does a Lambda become cold again? → AWS doesn't guarantee a fixed idle timeout — the execution environment may be recycled at any time after being idle. The timing isn't published and isn't guaranteed. If consistent low latency is critical, use Provisioned Concurrency.
The Lambda Init Phase
The Init Phase occurs only during a cold start. It's the window between "Lambda decided to run your function" and "Lambda actually calls your handler."
What runs during Init Phase:
Everything outside the handler function — connection setup, SDK initialization, loading configuration, reading secrets from Secrets Manager or Parameter Store.
Example — good production pattern:
import boto3
import os
# This runs ONCE during Init Phase (cold start only)
dynamodb = boto3.resource('dynamodb')
table = dynamodb.Table(os.environ['TABLE_NAME'])
secret = get_secret() # loaded once, reused on warm invocations
def lambda_handler(event, context):
# This runs on EVERY invocation
result = table.get_item(Key={'id': event['id']})
return result
Why this matters in production:
If you initialize your DynamoDB client inside the handler, it gets created on every single invocation — wasting time and compute. If you initialize it outside (at module level), it's created once during the cold start and reused on all subsequent warm invocations — dramatically reducing per-invocation latency and cost.
🎯 Best practice: Always initialize reusable resources (DB connections, SDK clients, loaded configuration, decrypted secrets) outside the handler. They survive across warm invocations. Inside the handler, only put code that must run per-invocation.
Provisioned Concurrency — Eliminating Cold Starts
For latency-sensitive production workloads (APIs where users are waiting for a response), cold starts are unacceptable. Provisioned Concurrency is the solution.
Provisioned Concurrency keeps a specified number of Lambda execution environments pre-initialized and ready — the Firecracker microVM is running, the runtime is loaded, and your Init Phase code has already executed. When a request arrives, it immediately hits a warm environment, with zero cold start latency.
Important distinction:
Provisioned Concurrency does NOT send dummy requests to keep the function warm — it physically keeps initialized execution environments alive in a ready state. There's no artificial traffic, just pre-warmed infrastructure.
The cost trade-off:
You pay for provisioned concurrency even when no requests are coming in — you're paying to keep those environments alive. This is a deliberate trade-off: spend slightly more to guarantee consistent low latency for your users.
When to use it:
- Customer-facing APIs where p99 latency matters
- Payment or checkout flows where slow responses lose sales
- Any Lambda function behind API Gateway serving real users
When NOT to use it:
- Background processing (users aren't waiting)
- Dev/test environments
- Functions with predictable low traffic
Best Practices for Production Lambda
These come directly from what you learned in class, framed as actionable rules:
1. Keep your deployment package small. Every MB of package size adds cold start latency because Lambda must download and unpack it. Use Lambda Layers for shared dependencies and avoid bundling unused libraries.
2. Initialize reusable resources outside the handler. Database connections, SDK clients, secrets — put them at module level so they're created once (Init Phase) and reused across warm invocations.
3. Set timeouts deliberately. Don't leave the default timeout at 3 seconds blindly — understand your function's expected runtime and set a meaningful timeout. Too short = unexpected failures. Too long = runaway functions costing money.
4. Keep logging meaningful. Every log line your function emits goes to CloudWatch and is charged. In production, avoid debug-level logging on every invocation — log only errors, warnings, and meaningful business events.
5. Use environment variables for configuration. Never hardcode secrets, endpoints, or table names in your code. Use environment variables (or Secrets Manager for sensitive values) so the same code works across dev/staging/prod environments.
6. Limit function scope. One Lambda function should do one thing. A function that tries to do too much becomes hard to test, debug, and maintain.
Lambda Triggers
A trigger is an event source that invokes your Lambda function. Lambda functions don't run on their own — they always respond to something.
| Trigger | What causes it | Common use |
|---|---|---|
| S3 | File uploaded, deleted, or modified in a bucket | Image processing, ETL, notifications |
| API Gateway | HTTP request (GET, POST, PUT, DELETE) | REST APIs, webhooks |
| DynamoDB Streams | Item inserted, updated, or deleted in a DynamoDB table | Real-time data sync, audit logging |
| SQS | Message added to a queue | Decoupled background processing |
| SNS | Message published to a topic | Fan-out notifications, email, SMS |
| EventBridge | Scheduled rule or custom event | Cron jobs, automation pipelines |
| Kinesis | Records added to a data stream | Real-time analytics, log processing |
| ALB | HTTP request via Application Load Balancer | Serverless web applications |
| Cognito | User pool events (signup, login) | Custom auth flows, post-signup triggers |
🎯 Interview Q: What triggers a Lambda function? → Events from AWS services — S3 uploads, API Gateway requests, DynamoDB Streams, SQS messages, SNS notifications, EventBridge schedules, and more. Lambda is fundamentally event-driven.
Invocation Models — Synchronous, Asynchronous, Poll-Based
Not all Lambda triggers work the same way — the invocation model determines who calls Lambda, who waits for the response, and who handles retries on failure.
The Three Models
| Synchronous | Asynchronous | Poll-Based | |
|---|---|---|---|
| Examples | API Gateway, ALB, SDK direct call | S3, SNS, EventBridge | SQS, Kinesis, Kafka, DynamoDB Streams |
| Who triggers Lambda? | Caller directly | AWS service (push) | Lambda (polls the source) |
| Caller waits? | Yes — waits for response | No — fire and forget | N/A |
| Retry on failure | Caller's responsibility | Lambda retries automatically (up to 2 times) | Event source + Lambda polling logic |
| Failure destination | Error returned to caller | Dead Letter Queue or EventBridge | DLQ or failure destination |
Memory Tricks
Synchronous → 📞 Phone Call
You call someone and stay on the line waiting for their answer. If they don't pick up, you know immediately — you handle the retry.Asynchronous → 📧 Email
You send the email and move on with your day. The recipient processes it when they're ready. If delivery fails, the mail system retries, not you.Poll-Based → 📬 Checking Your Mailbox
You walk to the mailbox every few minutes to see if anything arrived. That's exactly what Lambda does with SQS, Kinesis, Kafka, and DynamoDB Streams — Lambda continuously polls the source and processes whatever it finds.
Deep Dive Per Model
Synchronous:
The caller waits for Lambda to finish and return a response. If Lambda throws an error, the error is returned directly to the caller — the caller (application code or API Gateway) decides whether to retry.
Asynchronous:
AWS queues the event and returns a 202 Accepted to the caller immediately — Lambda processes it when it can. If Lambda fails, AWS automatically retries up to 2 times. After all retries are exhausted, the failed event goes to a configured Dead Letter Queue (SQS or SNS) or an EventBridge failure destination for investigation.
Poll-Based:
Lambda itself polls the event source (SQS queue, Kinesis stream, DynamoDB Stream) in a loop. When it finds records, it invokes itself with a batch of them. This model is used for queue and stream processing — Lambda reads as fast as the source produces events.
🎯 Interview tip: "How does S3 invoke Lambda?" → Asynchronously — S3 pushes an event notification to Lambda, doesn't wait for a response, and Lambda retries automatically on failure. "How does API Gateway invoke Lambda?" → Synchronously — the HTTP response the user sees depends on Lambda's return value.
Execution Roles and Permissions
Lambda functions need IAM permissions to interact with other AWS services — they can't access S3, DynamoDB, or CloudWatch by default.
Execution Role = an IAM Role attached to a Lambda function that defines what AWS services and actions the function is allowed to call.
Example: Lambda triggered by S3, writing results to DynamoDB
The Execution Role for this function needs:
s3:GetObjecton the source bucket (to read the uploaded file)dynamodb:PutItemon the target table (to write the result)logs:CreateLogGroup,logs:CreateLogStream,logs:PutLogEventson CloudWatch (to write logs)
The last three — CloudWatch log permissions — are included in the AWS managed policy AWSLambdaBasicExecutionRole, which is the minimum every Lambda function needs.
Principle of Least Privilege applies here too: Give Lambda only the permissions it actually needs, nothing more. A function that reads from S3 shouldn't also have DynamoDB write permissions unless it genuinely needs them.
🎯 Common interview scenario: "Your Lambda function is failing with an AccessDenied error when trying to write to DynamoDB. What do you check first?" → Check the Lambda function's Execution Role — verify it has
dynamodb:PutItempermission on that specific table ARN.
Lambda in a VPC — Private Resource Access
The Default Setup
By default, Lambda runs inside an AWS-managed VPC — this gives it internet access for calling public AWS service endpoints or external APIs, but it cannot reach resources inside your private VPC — your RDS database, ElastiCache cluster, or private EC2 instances.
Connecting Lambda to Your VPC
To access private resources, you attach Lambda to your VPC by selecting subnets and Security Groups — similar to how you'd configure any other EC2-based resource.
Under the hood, AWS uses Hyperplane ENIs (pre-created, managed elastic network interfaces) to connect Lambda to your VPC. This is significantly faster than the older model where Lambda created a new ENI on every cold start — Hyperplane ENIs are shared and pre-warmed, which reduces cold start latency for VPC-attached functions.
The Internet Access Trade-Off
Key point: Once Lambda is attached to your VPC, it loses direct internet access. It now operates like any other private subnet resource.
This creates a dependency decision:
| Lambda needs to access | Solution |
|---|---|
| Private resources (RDS, ElastiCache, internal EC2) | Attach Lambda to your VPC, select private subnets |
| Public internet (third-party APIs, external services) | Route traffic through a NAT Gateway in a public subnet |
| AWS services (S3, DynamoDB, Secrets Manager) | Use VPC Endpoints — private connectivity without internet or NAT |
🎯 Interview Q: "Your Lambda function is attached to your VPC and can access your RDS database, but it can't reach an external payment API. What do you configure?" → Add a NAT Gateway to a public subnet in the same VPC. Update the private subnet's route table to route
0.0.0.0/0through the NAT Gateway. Lambda can then reach the internet through NAT while still accessing private resources directly.
Flow with full connectivity:
Lambda (in private subnet) → needs internet (external API) → route table sends to NAT Gateway (in public subnet) → NAT Gateway → Internet Gateway → external API
Lambda (in private subnet) → needs S3 or DynamoDB → VPC Gateway/Interface Endpoint → directly to AWS service, no internet involved
Lambda Pricing
Lambda billing has two components plus one commonly overlooked cost:
Component 1 — Requests:
You're charged per million invocations. The first 1 million requests per month are free.
Component 2 — Duration (GB-seconds):
Execution time multiplied by the memory you allocated. A function with 512 MB of memory running for 2 seconds = 1 GB-second of compute.
More memory = higher cost per second, but often faster execution (Lambda allocates CPU proportionally to memory). Sometimes giving a function more memory actually reduces cost because it finishes faster.
Component 3 — CloudWatch Logs (often overlooked):
Every print() statement, every log line your Lambda function emits goes to CloudWatch Logs — and CloudWatch charges for log ingestion and storage. For functions invoked millions of times per day, excessive logging becomes a real cost.
🎯 Production discipline: In production, log errors and important business events. Remove or disable debug-level logging that fires on every invocation. The savings add up at scale.
Free Tier (permanent, not just 12 months):
- 1 million requests/month
- 400,000 GB-seconds of compute/month
🧪 Lab — Lambda Triggered by S3
What you're building: Upload an image to S3 → Lambda is triggered → (in the assignment, Lambda resizes the image and saves the thumbnail back to S3)
Step 1 — Create the Lambda Function
AWS Console → Lambda → Create function
Choose: Author from scratch
Function name:s3-image-processor
Runtime: Python 3.12
Architecture: x86_64Execution role: Create a new role with basic Lambda permissions (adds CloudWatch Logs access automatically)
→ Create function
Step 2 — Configure S3 Trigger
In the Lambda function page → Add trigger → Select S3
Bucket: select your source bucket
Event type:PUT(triggers on file upload)
Prefix/Suffix: optionally filter to.jpgor.pngfiles only
→ Add
Step 3 — Write the Handler
import json
import boto3
def lambda_handler(event, context):
# Extract bucket and object key from the S3 event
bucket = event['Records'][0]['s3']['bucket']['name']
key = event['Records'][0]['s3']['object']['key']
print(f"New file uploaded: s3://{bucket}/{key}")
return {
'statusCode': 200,
'body': json.dumps(f'Processed: {key}')
}
Step 4 — Add Required Permissions to Execution Role
Lambda function → Configuration → Permissions → Click the execution role link → IAM console opens
Add inline policy or attach managed policy:
s3:GetObjecton the source buckets3:PutObjecton the destination bucket (for the assignment)
Step 5 — Test
Upload any file to your S3 bucket → Go to Lambda → Monitor → View CloudWatch logs
You should see the log line:New file uploaded: s3://your-bucket/your-file.jpg
⚡ Quick Revision
Lambda Core
- Serverless — no server management, auto-scales, pay per use
- Max timeout: 15 minutes
- Stateless — no persistent state between invocations
Cold Start vs Warm
- Cold: create microVM → load runtime → download code → Init Phase → run handler (slow)
- Warm: reuse existing environment → run handler directly (fast)
- AWS may recycle idle environments at any time — no guaranteed idle timeout
Init Phase
- Runs only on cold start
- Executes all code outside the handler (DB connections, SDK clients, secrets)
- Initialize reusable resources here — they survive across warm invocations
Provisioned Concurrency
- Keeps N environments pre-initialized → zero cold starts for those N concurrent requests
- Costs money even when idle — pay for consistent latency
Invocation Models
| Model | Memory trick | Who retries? |
|---|---|---|
| Synchronous | 📞 Phone call — wait for answer | Caller |
| Asynchronous | 📧 Email — send and move on | Lambda (auto, up to 2x) |
| Poll-Based | 📬 Check mailbox — Lambda polls | Event source + polling logic |
VPC Integration
- Default: Lambda in AWS VPC — has internet, no private resource access
- VPC-attached: Lambda in your VPC — access private resources, loses internet
- Fix for internet: add NAT Gateway
- Fix for AWS services: use VPC Endpoints (private, no internet needed)
Firecracker
- Lightweight microVM — full isolation, millisecond startup
- Powers Lambda and Fargate
Pricing
- Per request + per GB-second (memory × duration)
- CloudWatch Logs cost is easy to overlook — keep logging lean in production
💼 Interview Questions
Q1: What is the difference between Lambda and EC2?
EC2 is a persistent virtual machine you provision, manage, and pay for continuously — whether handling traffic or idle. Lambda is serverless — it runs only when triggered, scales automatically, charges only for the execution time used, and has zero infrastructure management overhead. EC2 is for long-running stateful services; Lambda is for event-driven, short-lived tasks.
Q2: What is a cold start in Lambda and how do you reduce it?
A cold start occurs when Lambda creates a new execution environment — it must initialize the Firecracker microVM, load the runtime, download the function code, and run the Init Phase before the handler executes. This adds latency. To reduce cold starts: keep the deployment package small, initialize reusable resources outside the handler, and use Provisioned Concurrency for latency-critical functions.
Q3: What is the difference between synchronous, asynchronous, and poll-based invocation?
Synchronous invocation (API Gateway, ALB) means the caller waits for Lambda's response and handles retries itself. Asynchronous invocation (S3, SNS, EventBridge) means AWS queues the event, returns immediately to the caller, and Lambda retries automatically on failure. Poll-based invocation (SQS, Kinesis, DynamoDB Streams) means Lambda itself continuously polls the source and processes records in batches.
Q4: Why does Lambda lose internet access when attached to a VPC?
Lambda in a VPC operates like any resource in a private subnet — it routes traffic through the subnet's route table. Since private subnets don't have a direct route to the Internet Gateway, Lambda has no internet path. To restore internet access, you add a NAT Gateway in a public subnet and route the private subnet's 0.0.0.0/0 traffic through it — the same pattern used for any private EC2 instance.
Q5: What is Provisioned Concurrency and when would you use it?
Provisioned Concurrency keeps a specified number of Lambda execution environments pre-initialized — the microVM, runtime, and Init Phase code are already ready. When requests arrive, they immediately hit warm environments with zero cold start latency. You'd use it for customer-facing APIs or payment flows where consistent low latency is critical. It costs money even when idle, so it's not appropriate for background or batch processing.
Q6: What should you initialize outside the Lambda handler vs inside it?
Outside the handler (at module level, runs once during Init Phase): database connection clients, SDK clients (boto3 sessions), secrets loaded from Secrets Manager, configuration loaded from environment variables, pre-computed static data. Inside the handler (runs on every invocation): logic that depends on the event itself — parsing the event, executing the business logic, returning the response.
Q7: Your Lambda function returns AccessDenied when calling DynamoDB. What do you check?
Check the Lambda function's Execution Role in IAM — verify it has the required DynamoDB permissions (dynamodb:GetItem, dynamodb:PutItem, etc.) on the correct table ARN. The Execution Role is the IAM identity Lambda uses when calling other AWS services, and missing permissions here is the most common cause of AccessDenied errors from Lambda.
📝 Assignment
Build an image resize pipeline: when an image is uploaded to an S3 bucket, Lambda automatically resizes it to a thumbnail and saves the result to a different S3 prefix or bucket.
AWS Session 13 — Lambda Fundamentals | Cloud + DevOps learning journey — Systems Engineer → Cloud/DevOps Engineer
Top comments (0)