All major clouds offer serverless compute, but they’ve evolved differently. AWS Lambda pioneered the space, Google Cloud Functions focuses on simplicity, and Azure Functions offers deep Microsoft ecosystem integration. This guide compares all three for real-world use.

Quick Comparison

FeatureAWS LambdaCloud FunctionsAzure Functions
Max timeout15 min60 min (2nd gen)230 min (Premium)
Max memory10 GB32 GB14 GB
Max package size250 MB (zipped)500 MB (2nd gen)Unlimited (Premium)
Cold startModerateLow (2nd gen)Moderate
Concurrency1000 default1000 defaultUnlimited (Premium)
Languages7 native + custom7 native7 native + custom
Container supportYesYes (2nd gen)Yes
Provisioned concurrencyYesMin instancesPremium plan
Free tier1M requests/mo2M requests/mo1M requests/mo

Lambda: The Original

Basic Function

# handler.py
import json
import boto3

dynamodb = boto3.resource('dynamodb')
table = dynamodb.Table('users')

def handler(event, context):
    """AWS Lambda handler."""
    
    # API Gateway event
    if 'httpMethod' in event:
        user_id = event['pathParameters']['id']
        response = table.get_item(Key={'id': user_id})
        
        return {
            'statusCode': 200,
            'headers': {'Content-Type': 'application/json'},
            'body': json.dumps(response.get('Item', {}))
        }
    
    # SQS event
    if 'Records' in event:
        for record in event['Records']:
            body = json.loads(record['body'])
            process_message(body)
        return {'batchItemFailures': []}

Terraform

resource "aws_lambda_function" "api" {
  function_name = "api"
  runtime       = "python3.12"
  handler       = "handler.handler"
  memory_size   = 512
  timeout       = 30
  
  filename         = "deployment.zip"
  source_code_hash = filebase64sha256("deployment.zip")
  role             = aws_iam_role.lambda.arn

  environment {
    variables = {
      TABLE_NAME = aws_dynamodb_table.users.name
    }
  }

  tracing_config {
    mode = "Active"
  }
}

resource "aws_lambda_function_url" "api" {
  function_name      = aws_lambda_function.api.function_name
  authorization_type = "NONE"
}

Lambda Strengths

  • Largest ecosystem — triggers for 200+ AWS services
  • Provisioned concurrency — eliminate cold starts
  • Lambda Layers — share code across functions
  • SnapStart — fast cold starts for Java
  • Event Source Mappings — native SQS, Kinesis, DynamoDB streams

Lambda Weaknesses

  • 15 minute limit — can’t run long jobs
  • VPC cold starts — slower when in VPC
  • IAM complexity — verbose policies required

Cloud Functions: The Simple Choice

Basic Function (2nd Gen)

# main.py
import functions_framework
from google.cloud import firestore

db = firestore.Client()

@functions_framework.http
def get_user(request):
    """HTTP Cloud Function."""
    user_id = request.args.get('id')
    
    if not user_id:
        return {'error': 'id required'}, 400
    
    doc = db.collection('users').document(user_id).get()
    
    if not doc.exists:
        return {'error': 'not found'}, 404
    
    return doc.to_dict()


@functions_framework.cloud_event
def process_pubsub(cloud_event):
    """Pub/Sub triggered function."""
    import base64
    import json
    
    data = base64.b64decode(cloud_event.data["message"]["data"])
    message = json.loads(data)
    
    # Process message
    print(f"Processing: {message}")

Terraform

resource "google_cloudfunctions2_function" "api" {
  name     = "api"
  location = "us-central1"

  build_config {
    runtime     = "python312"
    entry_point = "get_user"
    source {
      storage_source {
        bucket = google_storage_bucket.source.name
        object = google_storage_bucket_object.source.name
      }
    }
  }

  service_config {
    max_instance_count    = 100
    min_instance_count    = 1
    available_memory      = "512M"
    timeout_seconds       = 60
    service_account_email = google_service_account.function.email
  }
}

resource "google_cloud_run_service_iam_member" "invoker" {
  location = google_cloudfunctions2_function.api.location
  service  = google_cloudfunctions2_function.api.name
  role     = "roles/run.invoker"
  member   = "allUsers"
}

Cloud Functions Strengths

  • 60 minute timeout (2nd gen) — run longer jobs
  • 32 GB memory — handle memory-intensive workloads
  • Built on Cloud Run — familiar container model
  • Simpler IAM — service accounts are straightforward
  • Lower cold starts — 2nd gen is notably faster

Cloud Functions Weaknesses

  • Fewer triggers — not as many native integrations
  • Smaller ecosystem — fewer examples, libraries
  • Regional only — no global edge functions (use Cloud Run)

Azure Functions: The Enterprise Play

Basic Function

# function_app.py
import azure.functions as func
import json

app = func.FunctionApp()

@app.function_name(name="GetUser")
@app.route(route="users/{id}", auth_level=func.AuthLevel.ANONYMOUS)
def get_user(req: func.HttpRequest) -> func.HttpResponse:
    """HTTP trigger function."""
    user_id = req.route_params.get('id')
    
    # Get from Cosmos DB (binding would be cleaner)
    user = fetch_user(user_id)
    
    if not user:
        return func.HttpResponse(
            json.dumps({"error": "not found"}),
            status_code=404,
            mimetype="application/json"
        )
    
    return func.HttpResponse(
        json.dumps(user),
        mimetype="application/json"
    )


@app.function_name(name="ProcessQueue")
@app.service_bus_queue_trigger(
    arg_name="msg",
    queue_name="orders",
    connection="ServiceBusConnection"
)
def process_queue(msg: func.ServiceBusMessage):
    """Service Bus triggered function."""
    body = msg.get_body().decode('utf-8')
    data = json.loads(body)
    
    process_order(data)

Terraform

resource "azurerm_linux_function_app" "api" {
  name                = "api-functions"
  resource_group_name = azurerm_resource_group.main.name
  location            = azurerm_resource_group.main.location

  storage_account_name       = azurerm_storage_account.functions.name
  storage_account_access_key = azurerm_storage_account.functions.primary_access_key
  service_plan_id            = azurerm_service_plan.functions.id

  site_config {
    application_stack {
      python_version = "3.11"
    }
  }

  app_settings = {
    FUNCTIONS_WORKER_RUNTIME = "python"
    ServiceBusConnection     = azurerm_servicebus_namespace.main.default_primary_connection_string
  }

  identity {
    type = "SystemAssigned"
  }
}

resource "azurerm_service_plan" "functions" {
  name                = "functions-plan"
  resource_group_name = azurerm_resource_group.main.name
  location            = azurerm_resource_group.main.location
  os_type             = "Linux"
  sku_name            = "EP1"  # Elastic Premium
}

Azure Functions Strengths

  • Durable Functions — stateful workflows built-in
  • Premium plan — VNet integration, unlimited timeout
  • Best bindings — declarative I/O with many services
  • Windows support — .NET Framework compatibility
  • KEDA integration — event-driven autoscaling

Azure Functions Weaknesses

  • Cold starts — consumption plan can be slow
  • Complex plans — consumption vs premium vs dedicated
  • Storage dependency — requires storage account

Pricing Comparison

Scenario: 10M requests/month, 200ms average, 512MB memory

AWS Lambda:
  Requests: 10M × $0.20/M = $2.00
  Compute: 10M × 0.2s × 0.5GB × $0.0000166667 = $16.67
  Total: $18.67/month

Google Cloud Functions:
  Requests: 10M × $0.40/M = $4.00
  Compute: 10M × 0.2s × 0.5GB × $0.0000025 = $2.50
  Total: $6.50/month

Azure Functions (Consumption):
  Requests: 10M × $0.20/M = $2.00
  Compute: 10M × 0.2s × 0.5GB × $0.000016 = $16.00
  Total: $18.00/month

Winner: Cloud Functions (but check free tier impact)

With Free Tier

AWS Lambda: 1M free requests, 400,000 GB-seconds
GCF: 2M free requests, 400,000 GB-seconds  
Azure: 1M free requests, 400,000 GB-seconds

For low-volume apps, all are essentially free.

Cold Start Comparison

Measured p99 cold starts (512MB, Python):

PlatformCold StartWith VPC/VNet
Lambda~300ms~800ms
Cloud Functions 2nd gen~200ms~400ms
Azure (Consumption)~500ms~1000ms
Azure (Premium)~100ms~200ms

Mitigation Options

# Lambda: Provisioned Concurrency
resource "aws_lambda_provisioned_concurrency_config" "api" {
  function_name                     = aws_lambda_function.api.function_name
  qualifier                         = aws_lambda_alias.live.name
  provisioned_concurrent_executions = 5
}

# Cloud Functions: Min instances
resource "google_cloudfunctions2_function" "api" {
  service_config {
    min_instance_count = 1
  }
}

# Azure: Premium plan
resource "azurerm_service_plan" "functions" {
  sku_name = "EP1"
}

Event Sources Comparison

Trigger TypeLambdaCloud FunctionsAzure Functions
HTTP✅ API Gateway/URL✅ Native✅ Native
Queue✅ SQS✅ Pub/Sub✅ Service Bus/Queue
Storage✅ S3✅ Cloud Storage✅ Blob Storage
Database✅ DynamoDB Streams✅ Firestore✅ Cosmos DB
Schedule✅ EventBridge✅ Cloud Scheduler✅ Timer
IoT✅ IoT Core✅ IoT Core✅ IoT Hub
Streaming✅ Kinesis, MSK✅ Pub/Sub✅ Event Hubs

Language Support

LanguageLambdaCloud FunctionsAzure Functions
Python3.9-3.123.8-3.123.9-3.11
Node.js18.x, 20.x18, 2018, 20
Java8, 11, 17, 2111, 178, 11, 17
Go1.x1.18+Custom handler
.NET6, 86, 86, 8, Framework
Ruby3.2, 3.33.2Custom handler
RustCustom runtimeCustomCustom handler

Decision Matrix

Choose Lambda when:

  • You’re all-in on AWS
  • Need the broadest trigger ecosystem
  • Want SnapStart for Java
  • Complex IAM requirements

Choose Cloud Functions when:

  • Simplicity is priority
  • Need longer timeouts (60 min)
  • More memory needed (32 GB)
  • GCP-native workloads

Choose Azure Functions when:

  • Need Durable Functions (workflows)
  • Heavy Microsoft/.NET shop
  • Windows workloads
  • Need Premium plan features

Key Takeaways

  1. All three are production-ready — pick based on your cloud ecosystem
  2. Cloud Functions 2nd gen has best specs (60 min timeout, 32 GB RAM)
  3. Lambda has the largest ecosystem but $73/mo control plane adds up
  4. Azure Functions Durable is unique — built-in workflow orchestration
  5. Cold starts are solvable — provisioned concurrency/min instances/premium
  6. Pricing is similar — free tier makes low-volume nearly free everywhere
  7. Portability is a myth — event sources lock you in more than runtime

“Pick the serverless platform that matches your cloud. The 10% differences don’t matter as much as ecosystem fit.”