Serverless (Lambda, Functions)

Difficulty: Intermediate

Question

What is serverless computing? Explain AWS Lambda, its advantages and limitations, and when to use it.

Answer

Serverless computing lets you run code without provisioning or managing servers. The cloud provider handles all infrastructure: scaling, patching, availability. You pay only for actual compute time, not idle servers.

AWS Lambda is the most popular serverless platform. You upload a function, define a trigger (HTTP request, S3 upload, scheduled event, queue message), and Lambda runs your code when triggered. It scales automatically from zero to thousands of concurrent executions.

Advantages: - Zero server management and patching - Automatic scaling (including to zero when idle) - Pay-per-use billing (per 1ms of execution) - Built-in high availability across multiple zones

Limitations: - Cold starts: first invocation after idle period is slower (100ms-2s) - Execution timeout: maximum 15 minutes per invocation - Stateless: no local state between invocations - Vendor lock-in: code depends on cloud provider APIs - Debugging is harder than traditional servers

Ideal for: APIs with variable traffic, event processing (image resizing, email sending), scheduled tasks (cron jobs), and webhooks. Not ideal for: long-running processes, WebSocket connections, or applications with consistent high traffic (cheaper to run a server).

Code examples

AWS Lambda Function

// handler.ts - AWS Lambda function
import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda';

export const handler = async (
  event: APIGatewayProxyEvent
): Promise<APIGatewayProxyResult> => {
  try {
    const body = JSON.parse(event.body || '{}');
    const { name } = body;

    // Your business logic here
    const result = await processRequest(name);

    return {
      statusCode: 200,
      headers: {
        'Content-Type': 'application/json',
        'Access-Control-Allow-Origin': '*',
      },
      body: JSON.stringify({ message: `Hello, ${name}!`, data: result }),
    };
  } catch (error) {
    console.error('Error:', error);
    return {
      statusCode: 500,
      body: JSON.stringify({ error: 'Internal server error' }),
    };
  }
};

// S3 trigger example - resize uploaded images
export const imageResizer = async (event: S3Event) => {
  for (const record of event.Records) {
    const bucket = record.s3.bucket.name;
    const key = record.s3.object.key;
    // Download image, resize, upload thumbnail
    await resizeImage(bucket, key);
  }
};

Lambda functions receive an event object and return a response. API Gateway events include HTTP method, headers, body, and path parameters. S3 events trigger on file uploads.

Serverless Framework Deployment

# serverless.yml - Infrastructure as Code
service: myapp-api

provider:
  name: aws
  runtime: nodejs20.x
  region: us-east-1
  memorySize: 256
  timeout: 10
  environment:
    DATABASE_URL: ${ssm:/myapp/prod/database-url}
    JWT_SECRET: ${ssm:/myapp/prod/jwt-secret}

functions:
  getUsers:
    handler: src/handlers/users.getAll
    events:
      - http:
          path: /api/users
          method: get
          cors: true

  createUser:
    handler: src/handlers/users.create
    events:
      - http:
          path: /api/users
          method: post
          cors: true

  processImage:
    handler: src/handlers/images.resize
    events:
      - s3:
          bucket: my-uploads
          event: s3:ObjectCreated:*
          rules:
            - suffix: .jpg

  dailyReport:
    handler: src/handlers/reports.generate
    events:
      - schedule: cron(0 9 * * ? *)  # 9 AM daily

# Deploy
# npx serverless deploy
# npx serverless deploy --stage production

The Serverless Framework defines Lambda functions, their triggers, and infrastructure in YAML. One command deploys everything: functions, API Gateway, S3 triggers, and scheduled events.

Cold Start Mitigation

// Cold start happens when Lambda creates a new execution environment
// Strategies to minimize impact:

// 1. Keep functions warm with scheduled pings
// serverless.yml:
// functions:
//   api:
//     handler: handler.main
//     events:
//       - schedule: rate(5 minutes)  # ping every 5 min

// 2. Minimize bundle size (faster initialization)
// Use esbuild or webpack to tree-shake
// Avoid large SDKs - import only what you need:
import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3';
// NOT: import AWS from 'aws-sdk';  // imports everything

// 3. Initialize outside the handler (reused between invocations)
import { PrismaClient } from '@prisma/client';

// This runs ONCE when the container starts (cold start)
const prisma = new PrismaClient();

// This runs on EVERY invocation (reuses the connection)
export const handler = async (event: APIGatewayProxyEvent) => {
  const users = await prisma.user.findMany();
  return { statusCode: 200, body: JSON.stringify(users) };
};

// 4. Provisioned Concurrency (costs money but eliminates cold starts)
// aws lambda put-provisioned-concurrency-config \
//   --function-name myFunction \
//   --qualifier prod \
//   --provisioned-concurrent-executions 5

// Cold start times (approximate):
// Node.js:  ~200-500ms
// Python:   ~200-500ms
// Java:     ~3-10 seconds
// Go/Rust:  ~50-100ms

Initialize expensive resources (DB connections, SDK clients) outside the handler so they persist between invocations. Use the v3 AWS SDK for smaller bundles. Provisioned Concurrency eliminates cold starts.

Key points

Concepts covered

Serverless, AWS Lambda, API Gateway, Cold Start, Event-Driven, FaaS