Lambda vs Cloud Functions vs Azure Functions: Serverless Face-Off
Compare AWS Lambda, Google Cloud Functions, and Azure Functions head-to-head. Learn the differences in features, pricing, and developer experience.
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
| Feature | AWS Lambda | Cloud Functions | Azure Functions |
|---|---|---|---|
| Max timeout | 15 min | 60 min (2nd gen) | 230 min (Premium) |
| Max memory | 10 GB | 32 GB | 14 GB |
| Max package size | 250 MB (zipped) | 500 MB (2nd gen) | Unlimited (Premium) |
| Cold start | Moderate | Low (2nd gen) | Moderate |
| Concurrency | 1000 default | 1000 default | Unlimited (Premium) |
| Languages | 7 native + custom | 7 native | 7 native + custom |
| Container support | Yes | Yes (2nd gen) | Yes |
| Provisioned concurrency | Yes | Min instances | Premium plan |
| Free tier | 1M requests/mo | 2M requests/mo | 1M 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):
| Platform | Cold Start | With 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 Type | Lambda | Cloud Functions | Azure 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
| Language | Lambda | Cloud Functions | Azure Functions |
|---|---|---|---|
| Python | 3.9-3.12 | 3.8-3.12 | 3.9-3.11 |
| Node.js | 18.x, 20.x | 18, 20 | 18, 20 |
| Java | 8, 11, 17, 21 | 11, 17 | 8, 11, 17 |
| Go | 1.x | 1.18+ | Custom handler |
| .NET | 6, 8 | 6, 8 | 6, 8, Framework |
| Ruby | 3.2, 3.3 | 3.2 | Custom handler |
| Rust | Custom runtime | Custom | Custom 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
- All three are production-ready — pick based on your cloud ecosystem
- Cloud Functions 2nd gen has best specs (60 min timeout, 32 GB RAM)
- Lambda has the largest ecosystem but $73/mo control plane adds up
- Azure Functions Durable is unique — built-in workflow orchestration
- Cold starts are solvable — provisioned concurrency/min instances/premium
- Pricing is similar — free tier makes low-volume nearly free everywhere
- 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.”