by QL4B Team

The Lambda Runtime Spectrum: From Simple to Sophisticated

How to choose the right level of complexity for your serverless functions


The Runtime Complexity Problem

AWS Lambda gives you runtime choices: Python, Node.js, Java, Go, .NET, Ruby. But what if your problem doesn’t need a “real” programming language?

What if you just need curl + jq?

The Traditional Approach

The problem: You need an API that calls another API and transforms the response.

The default thinking: “I need to build an API, so I need a web framework in a real language.”

The result:

const axios = require('axios');
exports.handler = async (event) => {
  const response = await axios.get(externalUrl);
  return { statusCode: 200, body: JSON.stringify(response.data) };
};

The overhead: Runtime initialization, dependency loading, JSON parsing libraries, HTTP client abstractions.

The Spectrum Approach

Instead of one-size-fits-all, what if you could choose the right level of complexity?

Level 1: Simple Bash Functions

When to use: Basic HTTP calls, simple data transformation, file processing

Runtime: provided.al2023 with custom bootstrap Package: Zip artifact with shell scripts Memory: ~15MB Cold start: ~200ms

#!/bin/bash
api_handler() {
    local event="$1"
    curl -s "https://api.example.com/data" | jq '.results'
}

Perfect for:

  • HTTP proxies
  • Simple data transformation
  • File processing
  • Basic automation

Level 2: Enhanced with Layers

When to use: Need specific CLI tools beyond basic shell

Runtime: provided.al2023 + pre-built layers Available tools: jq, qrencode, htmlq, imagemagick, pandoc, sqlite, yq Memory: ~25MB Cold start: ~250ms

#!/bin/bash
qr_generator() {
    local text="$1"
    echo "$text" | qrencode -o - -t PNG | base64
}

html_parser() {
    local url="$1"
    curl -s "$url" | htmlq '.title' --text
}

Perfect for:

  • Web scraping with HTML parsing
  • Image processing
  • QR code generation
  • Document conversion
  • Data extraction

Level 3: Custom Runtime (Tiny)

When to use: Need consistent tooling, container deployment

Runtime: Custom container with jq, curl, http-cli Size: 132MB Memory: ~30MB Cold start: ~300ms

#!/bin/bash
api_client() {
    local endpoint="$1"
    local params="$2"
    
    http-cli --json "$endpoint" \
        --data "$params" \
        --timeout 10 \
        | jq '.data | map(select(.active == true))'
}

Perfect for:

  • Production HTTP APIs
  • JSON-heavy processing
  • Consistent deployment pipeline

Level 4: Custom Runtime (Micro)

When to use: Need AWS API calls without full CLI overhead

Runtime: Custom container + awscurl Size: 221MB Memory: ~50MB Cold start: ~400ms

#!/bin/bash
s3_processor() {
    local bucket="$1"
    local key="$2"
    
    # Get object metadata
    awscurl --service s3 "https://s3.amazonaws.com/$bucket/$key" \
        --request HEAD \
        | grep -i last-modified
}

Perfect for:

  • AWS service integration
  • S3 operations
  • Simple AWS API calls

Level 5: Custom Runtime (Full)

When to use: Need complete AWS CLI functionality

Runtime: Custom container + full AWS CLI Size: 417MB Memory: ~80MB Cold start: ~500ms

#!/bin/bash
infrastructure_manager() {
    local action="$1"
    
    case "$action" in
        "backup")
            aws s3 sync /data s3://backup-bucket/$(date +%Y%m%d)
            ;;
        "deploy")
            aws cloudformation deploy --template-file template.yml
            ;;
    esac
}

Perfect for:

  • Infrastructure automation
  • Complex AWS operations
  • Multi-service orchestration

Choosing the Right Level

Ask these questions:

  1. What tools do I actually need?

    • Just curl + jq? → Level 1
    • Need image processing? → Level 2
    • Need AWS APIs? → Level 4
  2. What’s my performance requirement?

    • Sub-200ms response? → Level 1-2
    • Can tolerate 500ms cold start? → Level 5
  3. What’s my deployment preference?

    • Simple zip upload? → Level 1-2
    • Container-based CI/CD? → Level 3-5
  4. What’s my complexity tolerance?

    • Keep it simple? → Level 1
    • Need production features? → Level 3+

The Anti-Pattern

Don’t start with Level 5 and work down.

Most developers start with “I need the full AWS CLI” and end up with 400MB containers for simple HTTP calls.

Start with Level 1 and work up.

Begin with the simplest solution. Add complexity only when you hit real limits.

Real-World Examples

HTTP Proxy API:

  • Started with Node.js (traditional)
  • Moved to Level 1 (bash + curl)
  • Result: 2x faster, 90% less memory

Image Processing Service:

  • Could use Level 5 (full runtime)
  • Actually needs Level 2 (imagemagick layer)
  • Result: 50% smaller, faster deployment

AWS Resource Monitor:

  • Tempting to use Level 5 (full CLI)
  • Level 4 (awscurl) handles 80% of cases
  • Result: Faster cold starts, smaller footprint

The Infrastructure Philosophy

The runtime should match the problem, not your assumptions.

This is why I built the lambda-shell-runtime spectrum:

  • Give developers the choice
  • Make simple things simple
  • Make complex things possible
  • No vendor lock-in at any level

Conclusion

Not every Lambda function needs a programming language.

Sometimes you need the full power of Python or Node.js. Sometimes you just need curl.

The infrastructure should get out of your way and let you choose the right tool for the job.

Start simple. Scale complexity only when necessary.


Part of the cloudless philosophy - infrastructure that gets out of your way.