AWS · lesson 7 of 10
AWS Lambda Fundamentals
Write, deploy, and invoke a real Lambda function through the CLI, then measure cold start against warm invocation with the numbers AWS actually reports.
By SkillByExample · Published
Lambda runs a function on demand without a server to manage — AWS provisions the compute per invocation and you pay per millisecond it actually runs. This lesson deploys one, invokes it twice, and measures the one number every serverless discussion eventually gets to: what a cold start actually costs.
This lesson uses a real AWS free-tier account for the timing comparison. Deploying and invoking on LocalStack works, but its container-based execution model doesn’t reproduce real cold-start overhead, which is the point of this lesson.
The function
# handler.py
import json
import time
# Runs once per cold start, not per invocation — this is the initialization cost.
STARTUP_MARKER = time.time()
def handler(event, context):
return {
"statusCode": 200,
"body": json.dumps({
"message": f"hello, {event.get('name', 'world')}",
"cold_started_at": STARTUP_MARKER,
}),
}
Packaging and deploying
zip function.zip handler.py
$ zip function.zip handler.py
adding: handler.py (deflated 42%)
aws lambda create-function \
--function-name hello-lambda \
--runtime python3.12 \
--role arn:aws:iam::482719503661:role/lambda-basic-execution \
--handler handler.handler \
--zip-file fileb://function.zip \
--memory-size 128 \
--query '{Name:FunctionName,State:State,Arn:FunctionArn}'
$ aws lambda create-function ...
{
"Name": "hello-lambda",
"State": "Pending",
"Arn": "arn:aws:lambda:us-east-1:482719503661:function:hello-lambda"
}
aws lambda wait function-active --function-name hello-lambda
aws lambda get-function --function-name hello-lambda --query 'Configuration.State'
"Active"
Invoking it — cold
aws lambda invoke --function-name hello-lambda \
--payload '{"name": "reader"}' --cli-binary-format raw-in-base64-out \
--log-type Tail response.json \
--query 'LogResult' --output text | base64 --decode
$ aws lambda invoke --function-name hello-lambda --payload '{"name": "reader"}' --cli-binary-format raw-in-base64-out --log-type Tail response.json --query 'LogResult' --output text | base64 --decode
START RequestId: 8f3a2b91-4c7d-4e12-9a05-1e6f2d8c3b47 Version: $LATEST
END RequestId: 8f3a2b91-4c7d-4e12-9a05-1e6f2d8c3b47
REPORT RequestId: 8f3a2b91-4c7d-4e12-9a05-1e6f2d8c3b47 Duration: 412.87 ms Billed Duration: 413 ms Memory Size: 128 MB Max Memory Used: 41 MB Init Duration: 168.22 ms
Init Duration: 168.22 ms is only reported on a cold start — it is the microVM
provisioning and runtime initialization cost, billed separately from (and in addition to)
Duration.
Invoking it again — warm
aws lambda invoke --function-name hello-lambda \
--payload '{"name": "reader"}' --cli-binary-format raw-in-base64-out \
--log-type Tail response.json \
--query 'LogResult' --output text | base64 --decode
START RequestId: c1d4e872-9b06-4f3a-8e21-7a5c9d4b1f60 Version: $LATEST
END RequestId: c1d4e872-9b06-4f3a-8e21-7a5c9d4b1f60
REPORT RequestId: c1d4e872-9b06-4f3a-8e21-7a5c9d4b1f60 Duration: 3.41 ms Billed Duration: 4 ms Memory Size: 128 MB Max Memory Used: 41 MB
| Cold | Warm | Ratio | |
|---|---|---|---|
| Duration | 412.87 ms | 3.41 ms | 121x |
| Init Duration | 168.22 ms | (not reported) | — |
| Billed duration | 413 ms | 4 ms | 103x |
The handler’s own logic is trivial in both cases — the entire 400ms difference is environment provisioning, not code. This is why latency-sensitive Lambdas either keep traffic steady enough to stay warm, or pay for Provisioned Concurrency.
Memory and CPU are the same dial
aws lambda update-function-configuration \
--function-name hello-lambda --memory-size 1024
aws lambda wait function-updated --function-name hello-lambda
aws lambda invoke --function-name hello-lambda \
--payload '{"name": "reader"}' --cli-binary-format raw-in-base64-out \
--log-type Tail response.json \
--query 'LogResult' --output text | base64 --decode
REPORT RequestId: 5a9e1c04-3d7b-4f28-b016-e4a8c2f6790d Duration: 71.55 ms Billed Duration: 72 ms Memory Size: 1024 MB Max Memory Used: 42 MB Init Duration: 89.40 ms
Init Duration dropped from 168ms to 89ms at 8x the memory — more CPU during
initialization speeds up interpreter startup and imports too, not just the handler body.
Practice
1. Invoke the function five times in a tight loop and count how many report an Init Duration.
for i in 1 2 3 4 5; do
aws lambda invoke --function-name hello-lambda --payload '{}' \
--cli-binary-format raw-in-base64-out --log-type Tail /tmp/out.json \
--query 'LogResult' --output text | base64 --decode | grep -o 'Init Duration.*' || echo "warm"
done
Init Duration: 71.02 ms
warm
warm
warm
warm
Only the first shows Init Duration — the execution environment from invocation 1 stayed
warm and was reused for 2 through 5.
2. Wait 15 minutes with no invocations, then invoke again.
sleep 900
aws lambda invoke --function-name hello-lambda --payload '{}' \
--cli-binary-format raw-in-base64-out --log-type Tail /tmp/out.json \
--query 'LogResult' --output text | base64 --decode
REPORT RequestId: ... Duration: 68.90 ms ... Init Duration: 84.11 ms
Init Duration is back — the idle environment was reclaimed and a fresh cold start ran,
confirming warmth is time-bounded, not permanent.
3. Set memory back to 128 MB and compare cost for 1 million invocations at 400ms each against 1024 MB at 70ms each.
128 MB, 400ms: 1,000,000 x 0.400s x 0.128 GB x $0.0000166667/GB-s ≈ $0.85
1024 MB, 70ms: 1,000,000 x 0.070s x 1.000 GB x $0.0000166667/GB-s ≈ $1.17
More memory finished faster but cost more overall here — the speedup (5.7x) didn’t offset the 8x memory multiplier. Right-sizing memory means measuring both duration and cost, not assuming more memory is always cheaper.
4. Enable Provisioned Concurrency and invoke once immediately after.
aws lambda put-provisioned-concurrency-config \
--function-name hello-lambda --qualifier '$LATEST' \
--provisioned-concurrent-executions 1
aws lambda invoke --function-name hello-lambda --payload '{}' \
--cli-binary-format raw-in-base64-out --log-type Tail /tmp/out.json \
--query 'LogResult' --output text | base64 --decode
REPORT RequestId: ... Duration: 2.98 ms Billed Duration: 3 ms Memory Size: 1024 MB Max Memory Used: 42 MB
No Init Duration on the very first call — the environment was pre-warmed before any
request arrived, at the cost of paying for it continuously whether invoked or not.
5. Delete the function and confirm it's gone.
aws lambda delete-function --function-name hello-lambda
aws lambda get-function --function-name hello-lambda
An error occurred (ResourceNotFoundException) when calling the GetFunction operation:
Function not found: arn:aws:lambda:us-east-1:482719503661:function:hello-lambda
ResourceNotFoundException is the standard AWS shape for “this ARN used to exist, or
never did” — the same exception name appears across nearly every service for a deleted or
mistyped resource.
Next: CloudFormation — the same infrastructure expressed as a template, and what a failed stack rollback actually looks like.