Serverless Was Overhyped (But the Right Parts Won)

“Serverless” didn’t fail because the technology was bad—it failed because people tried to use it for the one thing it was never designed to be great at. In the 2018–2020 boom cycle, teams treated Lambda like a universal solvent for application architecture. The result was predictable: broken assumptions, latency surprises, and brittle designs that collapsed under real traffic and real state.
But here’s the revisionist truth worth keeping: serverless didn’t replace servers. It carved out a few genuinely excellent niches. And if you use it for those niches—event-driven processing, scheduled jobs, webhook handlers, and the kind of pipelines that naturally flow through queues and storage—it’s not just acceptable. It’s often the best tool in the box.
The “serverless everything” mistake⌗
The hype train sold a simple promise: stop managing servers, scale automatically, pay only for what you use. That’s all true—at least at the level of individual services. The problem was how quickly “no servers” became a design philosophy instead of an implementation detail.
Most applications aren’t stateless monoliths you can decompose into independent functions with zero friction. They need at least some of the following:
- Persistent connections (WebSockets, long-lived database sessions, streaming responses)
- Local state that lives for the lifetime of a request flow
- Predictable latency for interactive UX
- Operational habits that assume warm process lifecycles and stable execution contexts
Serverless can sometimes mimic these patterns, but the cost is usually hidden complexity—clever caching strategies, fragile workarounds for state, and “best effort” latency management. Teams learned the hard way that architecture is about constraints, not slogans.
My favorite example from that era: the “Lambda behind API Gateway for a REST API” pattern used as a default. For lightweight endpoints, it can work. But when you wrap every endpoint in a serverless façade without thinking through connection behavior, timeouts, request/response size, observability, and warm-up realities, you end up optimizing for convenience instead of performance and reliability. In many cases, the experience wasn’t dramatically better than running a small fleet of containers—and the failure modes were different enough to make troubleshooting more annoying.
Why serverless never meant “no state,” just “no babysitting”⌗
Serverless functions are ephemeral execution environments. That does not mean you can’t build stateful systems. It means you must externalize state.
That distinction is where teams either matured—or burned time. A healthy serverless architecture assumes:
- Compute is disposable.
- State lives outside the function.
- You design for idempotency because retries happen.
- You treat timeouts and concurrency as first-class constraints.
So instead of trying to keep “session state” in memory, you store it in something like Redis or a database. Instead of assuming a function will always run once, you design for “run it again safely.” Instead of expecting a request to have unbounded duration, you set time budgets and push longer workflows into asynchronous pipelines.
This framing turns “serverless limitations” into straightforward engineering constraints. The system stops being magical and starts being predictable.
The right pattern: event-driven processing (not everything behind HTTP)⌗
The most natural serverless use case is boring in the best way: events in, work out. When your inputs come from systems that already speak in events—object storage uploads, message queues, webhook notifications—Lambda shines because it can scale compute to match incoming demand.
S3 uploads → image pipeline⌗
Take image processing. A user uploads an image. That’s an event. You don’t need an HTTP server sitting there for the rest of time; you need a pipeline:
- Upload to S3
- Trigger processing (e.g., resize, compress, generate thumbnails)
- Store results back to S3
- Optionally notify downstream services or update metadata
In practice, you get a workflow like “S3 event → function → write outputs.” The user’s upload and the derived processing are decoupled. If the pipeline is slower, it doesn’t stall user-facing traffic. If you need to add another step—say, watermarking—you extend the pipeline without reshaping the entire application.
SQS messages → background jobs⌗
Queues are another sweet spot because they’re fundamentally aligned with serverless execution models. You receive a message, do the work, acknowledge, repeat. If processing fails, retries happen. The important part is that you implement idempotency so “at least once delivery” doesn’t become “duplicate side effects.”
A practical rule: make your work conditional on a stable identifier. For example, if a message represents “process order 123,” write results keyed by order ID, and safely no-op if the work already completed.
This is also where operational sanity improves. Instead of scaling an application tier and managing job runners, you scale the event consumer and let the queue absorb bursts.
Webhooks → quick, durable responses⌗
Webhook handlers are another clean fit: receive an event from an external system, validate it, record it, trigger async follow-up work. The key is to keep the synchronous part short—ack quickly, then do heavier processing asynchronously.
If you try to do “everything” in the webhook request, you’ll hit timeouts and create cascading retries. If you treat the webhook as a trigger and move the real work to a queue or pipeline, serverless becomes an advantage rather than a gamble.
Scheduled jobs: let time be the trigger⌗
Not every useful workload starts with an external event. Some starts with time: nightly reports, cache refreshes, cleanup tasks, batch processing windows.
Serverless scheduled invocations are ideal for these because you avoid building and maintaining the “cron runner” infrastructure. You also get clear boundaries: each job run is its own execution. That makes debugging and reruns easier.
A practical pattern is “scheduled function → enqueue work.” Don’t have the scheduler do heavy lifting itself if the task can fan out. For example:
- Scheduled run at 02:00
- Query for “items needing refresh”
- Enqueue one message per item (or per shard)
- Worker functions process in parallel
This reduces the blast radius of failures and prevents one long-running job from turning into a recurring outage.
The latency and connection reality (and how to work with it)⌗
The hype glossed over the fact that serverless is not optimized for every interaction pattern. Execution time, cold starts, concurrency behavior, and integration timeouts are constraints you must respect.
So how do you build well anyway?
Design for short synchronous work⌗
Use serverless for the part of the request that benefits from elasticity and isolation. If a request needs slow external calls or heavy computation, push it into an async workflow. Then return something like:
- “Accepted” with a job ID
- Or update the UI via polling/websocket later
Even if you’re using serverless behind HTTP for specific endpoints, you still want the synchronous path to be lean.
Externalize dependencies and caches⌗
If you’re repeatedly calling downstream services, cache aggressively outside the function so you’re not depending on warm invocations. Treat caches as best-effort, not correctness.
Make retries boring⌗
Assume your function might run multiple times for the same logical event. That means:
- Use unique keys for side effects
- Deduplicate where possible
- Write results in a way that can be replayed safely
This is one of the most important “culture shifts” serverless demands. Once teams embrace it, the system becomes sturdier rather than fragile.
When you should avoid Lambda (and what to use instead)⌗
The blunt take: if your workload needs stable, long-lived connections or you’re building a real-time system with tight latency expectations, serverless may fight you. Likewise, if you want to manage complex in-memory state across requests, you’re probably recreating a server—just with extra steps.
In those cases, you’ll often be better off with:
- Long-running containers or services for streaming and persistent connections
- Managed databases and queues paired with a service tier where appropriate
- Event-driven services where serverless is a worker, not the entire application boundary
And if you’re thinking about doing “Lambda everywhere behind API Gateway,” do it selectively. Start with endpoints that are naturally request/response and lightweight—or endpoints that are easy to observe and easy to retry safely. Don’t make it your default architecture just because it’s trendy.
Conclusion: serverless didn’t die—it grew up⌗
The serverless everything era was overconfident, and it paid the price. But the core idea wasn’t wrong; it was the application of the idea that was. Lambda didn’t replace servers—it found a niche where it’s genuinely excellent: event-driven processing, scheduled jobs, webhook handling, and image/asset pipelines that naturally decompose into asynchronous steps.
If you build with the grain of serverless—externalized state, idempotent work, short synchronous paths, and async fan-out—you get a system that’s easier to scale, easier to reason about, and often cheaper to operate. Serverless wasn’t the future of every workload. It was the future of the right workloads. And that future is still here.