DEV Community

Tejas Shinkar
Tejas Shinkar

Posted on

AWS Lambda — Serverless Compute, Cold Starts, Invocation Models & VPC Integration

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!"
    }
Enter fullscreen mode Exit fullscreen mode

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 while

Second invocation (warm):
Reuse existing environment → Run handler directly → Return response

After 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
Enter fullscreen mode Exit fullscreen mode

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:GetObject on the source bucket (to read the uploaded file)
  • dynamodb:PutItem on the target table (to write the result)
  • logs:CreateLogGroup, logs:CreateLogStream, logs:PutLogEvents on 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:PutItem permission 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/0 through 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_64

Execution 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 .jpg or .png files 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}')
    }
Enter fullscreen mode Exit fullscreen mode

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:GetObject on the source bucket
  • s3:PutObject on 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)