All posts
AWS
Cloud
Serverless

A Serverless "Hello, From Lambda!" with AWS Lambda

Build and deploy your first AWS Lambda function from scratch — function handler, test events, CloudWatch logs, and an API Gateway trigger.

October 21, 2021 · 3 min read · By Kshitiz Regmi

"AWS Lambda lets you run code without thinking about servers." AWS handles scaling, patching, and availability automatically — you focus purely on your function logic.

Why Go Serverless?

Traditional backend infrastructure requires provisioning servers, configuring load balancers, managing OS patches, and monitoring uptime 24/7. AWS Lambda eliminates all of that:

  • No servers to manage — AWS runs your code on demand
  • Auto-scaling — from 1 to thousands of concurrent executions automatically
  • Pay per use — billed by 1ms increments, only when your code runs
  • Event-driven — triggered by API calls, S3 uploads, DynamoDB streams, cron schedules, and more

Creating Your First Lambda Function

Step 1: Open the Lambda Console

  1. Log into the AWS Console and search for Lambda
  2. Click Create function
  3. Choose Author from scratch

Step 2: Configure

  • Function name: hello-lambda
  • Runtime: Python 3.11
  • Architecture: x86_64

Leave the default execution role — AWS creates one with CloudWatch logging permissions automatically.

Step 3: Write the Handler

import json

def lambda_handler(event, context):
    print("Received event:", json.dumps(event, indent=2))
    
    name = event.get("name", "World")
    
    return {
        "statusCode": 200,
        "headers": {"Content-Type": "application/json"},
        "body": json.dumps({
            "message": f"Hello, From Lambda! Hi {name}",
            "requestId": context.aws_request_id
        })
    }

The lambda_handler(event, context) signature is Lambda's entry point:

  • event: input data (dict) from the trigger — API Gateway, S3, EventBridge, etc.
  • context: runtime metadata — function name, remaining time, request ID

Step 4: Test the Function

Click TestCreate new test event:

{
  "name": "Kshitiz"
}

Run it. You'll see:

{
  "statusCode": 200,
  "body": "{\"message\": \"Hello, From Lambda! Hi Kshitiz\", \"requestId\": \"abc-123\"}"
}

Monitoring with CloudWatch

Every Lambda invocation streams logs to Amazon CloudWatch automatically. Navigate to Monitor → View CloudWatch Logs:

START RequestId: abc-123-def Version: $LATEST
Received event: {
  "name": "Kshitiz"
}
END RequestId: abc-123-def
REPORT RequestId: abc-123-def  Duration: 2.45 ms  Billed Duration: 3 ms  Memory Size: 128 MB  Max Memory Used: 38 MB

CloudWatch gives you logs, metrics, error rates, duration percentiles, and alarm triggers — all out of the box.

Adding an API Gateway Trigger

To call your Lambda over HTTP:

  1. Click Add triggerAPI Gateway
  2. Select Create a new APIREST API
  3. Security: Open (for testing — use auth in production)
  4. Click Add

AWS creates an HTTPS endpoint like:

https://abc123.execute-api.us-east-1.amazonaws.com/default/hello-lambda

Call it with curl:

curl -X POST https://abc123.execute-api.us-east-1.amazonaws.com/default/hello-lambda \
  -H "Content-Type: application/json" \
  -d '{"name": "Kshitiz"}'
{"message": "Hello, From Lambda! Hi Kshitiz", "requestId": "xyz-456"}

The Cold Start Problem

Lambda containers are ephemeral. The first invocation after a period of inactivity incurs a cold start — Lambda must spin up a new execution environment (~100–500ms). Subsequent invocations reuse the warm container (typically <10ms overhead).

Mitigation strategies:

  • Provisioned Concurrency — pre-warm containers (costs $)
  • Minimize package size — smaller deployment packages load faster
  • Keep the handler lean — move heavy initialization (DB connections, model loading) outside the handler to the module level, so it runs once and is reused across warm invocations
# Good: initialized once at module load, reused across warm invocations
import boto3
s3 = boto3.client('s3')

def lambda_handler(event, context):
    # s3 is already initialized — no cold-start overhead here
    response = s3.get_object(Bucket="my-bucket", Key=event["key"])
    return response["Body"].read().decode()

Lambda Limits

LimitValue
Max execution timeout15 minutes
Memory128 MB – 10 GB
Ephemeral storage (/tmp)512 MB – 10 GB
Deployment package50 MB (zipped)
Default concurrency1,000 (region-wide, adjustable)

When to Use Lambda

Good fit:

  • REST APIs (via API Gateway)
  • Event-driven processing (S3 uploads, SQS messages)
  • Scheduled jobs (cron via EventBridge)
  • Lightweight ML inference
  • ETL and data transformation

Not a good fit:

  • Jobs exceeding 15 minutes
  • Workloads needing persistent in-memory state
  • High-throughput streaming requiring sub-millisecond latency