All articles
169 articles · updated weekly See our Tools
All articles
Comparisons

Serverless: real benefits, limits, and hidden costs

Cold starts, vendor lock-in, and bills that spike at scale: what serverless marketing doesn't say — and when it's genuinely worth it anyway.

Serverless: real benefits, limits, and hidden costs
COVER · Comparisons

Someone in the architecture sprint said "let's go serverless, no servers to manage." Three months later, the AWS bill had tripled and the team was debugging cold starts at 2am in a Lambda that was supposed to be simple. Not a rare story.

Serverless solves real problems — and creates others the marketing doesn't mention. This post covers both sides.


What serverless actually delivers

The premise is legitimate: you write a function, the provider handles everything — provisioning, scaling, patching, availability. No idle servers. You pay per execution, not per hour of running instance.

For workloads with irregular traffic, this is genuinely good. A webhook receiving 50 calls per hour during the day and zero at night? Serverless. A processing job that runs once per hour for 30 seconds? Serverless. You're not paying for an idle t3.micro at 3am.

Auto-scaling works too. A Lambda function scales to thousands of parallel executions without you touching anything. For unpredictable spikes, that has real value — especially compared to the nightmare of manually provisioning EC2 capacity before a marketing campaign.

What works well:

  • Event-driven workloads: webhooks, queue processing, S3 triggers
  • APIs with variable and unpredictable traffic
  • Sporadic jobs: reports, exports, lightweight ETL
  • Prototyping and MVPs: zero operational overhead at the start

Cold start: the problem that didn't go away

Cold start is the delay that happens when a function has no warm instance available. The provider needs to spin up the container, load the runtime, initialize your application — and only then execute your logic.

Numbers vary significantly:

Runtime Typical cold start
Node.js (Lambda) 200–500ms
Python (Lambda) 100–400ms
Java (Lambda) 1–10s
.NET (Lambda) 1–5s

Java and .NET are the worst offenders because the JVM/CLR takes time to initialize. If you have a payment approval service in Java running on Lambda, a 5s cold start in production is unacceptable. The most common workaround — invoking the function periodically to keep it "warm" — is a hack with extra cost. You're literally paying to simulate a conventional server.

# Classic keepalive "solution" — avoid reaching this point
# EventBridge rule: rate(5 minutes) → Lambda
def handler(event, context):
    if event.get("source") == "keepalive":
        return {"status": "warm"}
    # actual logic here

AWS mitigated this with Provisioned Concurrency, but then you're paying for reserved capacity — which starts to look like a conventional instance, just more expensive.


Vendor lock-in: deeper than it looks

The implicit contract of serverless is more invasive than it appears in the first sprint.

Your Lambda function uses AWS-specific environment variables, API Gateway triggers, native SQS integration, CloudWatch Logs with their structure. Migrating this to Google Cloud Functions or Azure Functions isn't copy-paste — it's a rewrite.

The problem isn't just the function itself. It's the ecosystem:

  • Logs: CloudWatch has its own DSL. Migrating to another platform means migrating dashboards, alerts, observability queries.
  • Permissions: IAM roles and policies are AWS-specific. Permission granularity in GCP is different.
  • Native triggers: an S3 trigger calling Lambda has no direct equivalent in other clouds.

This isn't a reason to avoid serverless. It's a reason to be deliberate about where you use it. A background job function with simple I/O has tolerable lock-in. An entire microservices architecture with dozens of functions interconnected via native provider services — that's structural dependency.

For a broader view on how cloud models compare in this regard, the post public, private and hybrid cloud has the strategic perspective that complements this one.


The costs the calculator doesn't show

Marketing focuses on cost per execution. The problem is that execution is just one line on the bill.

Egress and data transfer

Every time your Lambda function calls an external service or returns data, there's potential transfer cost. Within the same region, a lot is free. But if your function processes images and returns data to a CDN in another region, or integrates with an RDS database in another AZ, egress shows up.

Observability

Lambda + CloudWatch Logs is free up to a point. But if you have high-volume production functions, the cost of detailed logs and metrics scales. X-Ray (distributed tracing) charges per recorded trace. For an architecture with 20 functions calling each other, observability costs can be comparable to execution costs.

Per-execution cost at high scale

The "pay for what you use" promise becomes a trap when usage is high and constant. Lambda charges per GB-second. A function with 512MB of memory running for 1 second costs $0.0000083325. Sounds cheap. But 10 million executions per month ≈ ~$83. 100 million ≈ ~$830. An EC2 t3.medium (2 vCPU, 4GB) costs ~$30/month.

Approximate break-even (Lambda 512MB vs t3.medium $30/month):
30 / 0.0000083325 ≈ 3.6 million executions/month of 1s each

Above that: EC2 can be cheaper.

This doesn't account for EC2 needing ops, configured auto-scaling, etc. But the point is: serverless isn't cheap at high scale, it's cheap at low-to-medium scale with irregular traffic.

The debugging cost

Serverless functions are harder to debug locally. You don't have a running process you can attach a debugger to. Reproducing a production problem requires simulating the execution environment, environment variables, IAM permissions — and even then it's not guaranteed. For development, this adds overhead that a conventional API doesn't have.


When not to use serverless

Cases where serverless makes things worse:

Workloads with persistent database connections. Lambda opens and closes connections on every execution. With RDS PostgreSQL, this means problematic connection pooling. The solution is RDS Proxy — which has additional cost and adds latency.

Applications with in-memory state. If your logic depends on local cache, in-memory session, or accumulated state between requests, each function instance starts from scratch. You need to externalize that state to Redis or DynamoDB, adding latency and cost.

Long-running processing. Lambda has a 15-minute timeout. If your data processing job sometimes takes 20 minutes depending on volume, you have a structural problem.

Latency-critical services with constant traffic. For APIs receiving constant traffic with latency SLAs, cold starts and Provisioned Concurrency costs make a conventional instance simpler and cheaper.


Frequently asked questions

Are serverless and containers competitors?

Not necessarily. Many architectures use both: containers for services with constant traffic and state, serverless functions for event-driven jobs and async processing. Lambda currently supports container execution, so the line got even blurrier. The choice is about access pattern and workload, not ideology.

Is cold start still a problem in 2026?

It improved, but didn't go away. AWS SnapStart (for Java) reduced cold starts from seconds to hundreds of milliseconds in many cases. Lighter runtimes like Node.js and Python have tolerable cold starts for most use cases. The problem persists for heavy runtimes and functions with large dependency packages. If latency is critical and traffic is irregular, Provisioned Concurrency solves it — but has cost.

Can serverless be tested locally?

Yes, but with friction. AWS SAM CLI and LocalStack simulate the environment. The problem is fidelity isn't 100%: retry behaviors, timeout limits, actual cold start behavior — these details only appear in the real environment. For development, this adds overhead that a conventional API doesn't have.

Why did my bill explode without warning?

Serverless has no cost ceiling by default. A misconfigured loop or a DDoS attack triggering your functions can generate a massive bill before you notice. AWS has AWS Cost Anomaly Detection and you can configure budget alerts — but it's your responsibility to configure them. By default, there's no limiter.


The tradeoff worth understanding

Serverless isn't better or worse than containers or instances. It's a different trade: you give up control and predictability in exchange for zero operational overhead and automatic scaling. That's great for the right cases.

The problem is when serverless is adopted as architectural dogma — "let's go serverless on everything" — without analyzing the access pattern of each service. The bill arrives later, along with the cold starts and lock-in.

Use serverless where the access pattern is irregular and the workload is stateless. Use containers or instances where traffic is constant, latency is critical, or processing runs long. To inspect response headers and status codes from your serverless APIs during debugging, the HTTP Status Codes reference is handy for understanding what's happening between client and function.

The decision isn't binary, and it doesn't have to be permanent.

RD
Author
Rafael Duarte
Backend developer with a background in fintech and B2B SaaS — worked on teams that scaled APIs from zero to millions of requests. Carries enough production scars to hold strong opinions on tools, patterns and architecture decisions. Not an academic: he read the UUID RFC when he had to choose between v4 and v7 for a write-heavy table.
View profile