Written by
Halkwinds Editorial Team
Halkwinds Research & Editorial

Serverless Architecture: When It Makes Sense (and When It Doesn't)
A clear-eyed look at serverless trade-offs — cold starts, vendor lock-in, observability challenges, and the workloads it's genuinely best for.
Serverless has moved from hype to default in many engineering conversations, and that's precisely why it deserves a harder look. For a CTO weighing infrastructure decisions, "just go serverless" is not a strategy — it's a bet on a specific set of trade-offs. Done well, serverless architecture eliminates undifferentiated operational toil, scales elastically with demand, and shifts your cost model from capacity planning to actual usage. Done poorly, it introduces cold-start latency into critical paths, deepens vendor lock-in, and creates observability blind spots that surface only during an incident. This article gives you a clear-eyed framework for deciding when serverless is the right call — and when a container or a boring old VM is the smarter, cheaper choice.
- Background / Why This Matters
- Core Concepts and Architecture
- Implementation Strategy
- Scaling and Operational Considerations
- Common Mistakes / What to Avoid
- Frequently Asked Questions
- Conclusion
Background / Why This Matters
The term "serverless" is a marketing simplification — there are still servers, you just don't manage them. What you're really buying is a shift in responsibility. With Function-as-a-Service (FaaS) platforms like AWS Lambda, Azure Functions, and container-based options like GCP Cloud Run, the provider owns provisioning, patching, autoscaling, and capacity. You own the code and the configuration. That reallocation of effort is the entire value proposition.
For a CTO, this matters because engineering time is your scarcest resource. Every hour a senior engineer spends tuning EC2 autoscaling groups, rotating AMIs, or debugging a Kubernetes node pool is an hour not spent on product. Serverless promises to reclaim that time. But the promise comes with conditions, and the failure mode is subtle: teams adopt serverless for a workload it suits, love it, then apply it uniformly to workloads it actively harms — long-running jobs, latency-sensitive APIs, or high-throughput services where the per-invocation pricing quietly balloons past what a reserved instance would cost.
The stakes are architectural and financial. Reversing a serverless-first decision after two years of accumulated Lambda functions, event triggers, and provider-specific glue is expensive and slow. Getting the decision right up front is a leadership responsibility, not an implementation detail.
Takeaway: Serverless is a responsibility-shifting trade, not a universal upgrade. Decide per workload, not per company.
Core Concepts and Architecture
Not all "serverless" is the same, and conflating the models leads to bad decisions. There are three broad categories worth distinguishing.
Function-as-a-Service (FaaS)
Discrete functions triggered by events — an HTTP request, a queue message, a file upload, a scheduled cron. AWS Lambda is the canonical example, with Azure Functions as its direct equivalent. You write a handler, the platform runs it in an ephemeral execution environment, and you pay per invocation and per gigabyte-second of memory-time. Functions are stateless by design; any state lives in an external store like DynamoDB, Redis, or S3.
Container-based serverless
GCP Cloud Run and AWS Fargate let you package a standard container and hand it to a managed runtime that scales it — sometimes to zero. This bridges the gap between FaaS and traditional containers: you keep Docker portability while shedding cluster management. Cloud Run in particular is attractive because your workload is just a container listening on a port, which dramatically reduces lock-in compared to a Lambda-native handler.
Managed backend services
Databases, queues, and auth that scale automatically and bill on usage — DynamoDB, Aurora Serverless, SQS, EventBridge. These are often the most valuable part of a serverless stack because they remove operational burden without imposing FaaS constraints on your application logic.
The cold start problem
When a function hasn't run recently, the platform must initialize a new execution environment before handling the request — the cold start. Depending on runtime and package size, this adds anywhere from tens of milliseconds to several seconds. For a nightly batch job, cold starts are irrelevant. For a synchronous API behind a mobile app, they can violate your latency budget. Provisioned concurrency (Lambda) and minimum instances (Cloud Run) mitigate this — but they also reintroduce a fixed cost, eroding the pay-per-use benefit.
Takeaway: Match the serverless model to the workload. Use FaaS for event-driven glue, container serverless for portable services, and managed backends almost everywhere.
Implementation Strategy
The single most useful exercise before committing is honest workload classification. Serverless rewards certain shapes and punishes others.
| Workload type | Serverless fit | Why |
|---|---|---|
| Event-driven processing (image resize, webhook handling) | Excellent | Bursty, stateless, short-lived — the ideal profile |
| Scheduled/cron jobs | Excellent | Zero idle cost; runs only when triggered |
| Low-to-medium traffic APIs | Good | Scales with demand; cold starts manageable with provisioning |
| High-throughput, steady-state APIs | Poor | Per-invocation cost exceeds reserved instances at scale |
| Long-running compute (video transcode, ML training) | Poor | Execution time limits and cost per GB-second make it uneconomical |
| Latency-critical, sub-50ms paths | Poor | Cold starts and network hops blow the latency budget |
Once you've classified workloads, structure the implementation around a few principles:
- Keep functions small and single-purpose. A function that does one thing is easier to test, deploy, and reason about. Avoid the "Lambda-lith" — a monolith stuffed inside one giant function with an internal router.
- Externalize all state. Assume any given invocation runs on a fresh, empty environment. Persist to DynamoDB, S3, or a managed database — never to local disk or in-memory globals you expect to survive.
- Use infrastructure-as-code from day one. Serverless multiplies the number of deployable units. Tools like AWS SAM, the Serverless Framework, Terraform, or AWS CDK keep function definitions, triggers, and IAM policies version-controlled and reviewable.
- Design for idempotency. Event sources like SQS and EventBridge can deliver a message more than once. Every function must handle duplicate invocations safely.
- Budget for cold starts explicitly. Choose lean runtimes (Go and Node.js typically start faster than the JVM), minimize dependency bloat, and reserve provisioned concurrency only for the latency-sensitive functions that need it.
This is exactly the kind of upfront architecture work where a second set of expert eyes pays for itself. Our Halkwinds cloud team frequently runs workload-classification workshops with client engineering leaders to sort what belongs on serverless from what belongs on containers or VMs — before a single function is written.
Takeaway: Classify every workload first, then adopt serverless only where the shape fits. Infrastructure-as-code and idempotency are non-negotiable from day one.
Scaling and Operational Considerations
Serverless scales beautifully — until it scales into something else you own. Autoscaling from zero to thousands of concurrent executions is genuinely magical, but it hits real ceilings you must plan for.
Downstream bottlenecks
Lambda will happily spin up 1,000 concurrent executions. Your relational database will not happily accept 1,000 simultaneous connections. This connection-storm problem is one of the most common serverless production incidents. Solutions include RDS Proxy, connection pooling, or moving to a connection-tolerant store like DynamoDB. The lesson: your function's scalability is capped by the least scalable thing it talks to.
Concurrency limits and quotas
Every provider imposes account-level concurrency limits. AWS Lambda has a default regional concurrency limit that, if hit, throttles invocations across all functions in the account. Reserve concurrency for critical functions and request limit increases before a launch, not during one.
Observability
This is where serverless demands the most maturity. A single user request may fan out across an API Gateway, three Lambda functions, an SQS queue, and a DynamoDB table. Traditional server-centric monitoring falls apart. You need distributed tracing — AWS X-Ray, OpenTelemetry, or third-party tools like Datadog and Honeycomb — to follow a request across the sprawl. Structured logging with a correlation ID threaded through every hop is essential. Without it, debugging a production issue means grepping through fragmented CloudWatch log groups at 2 a.m.
Cost observability
Serverless cost is a function of usage, which means a bug or a traffic spike translates directly into a bill. Set billing alarms, tag functions by team and feature, and review cost-per-transaction regularly. Estimates vary widely by workload, but many teams find serverless cheaper below a certain traffic threshold and more expensive above it — the crossover point is worth calculating for your specific case.
Takeaway: Your serverless app is only as scalable as its least scalable dependency. Invest in distributed tracing and cost alarms before you go to production, not after your first incident.
Common Mistakes / What to Avoid
- Treating serverless as a religion. The most damaging mistake is ideological commitment. Forcing a long-running ETL job or a high-QPS service into Lambda because "we're serverless-first" produces worse performance at higher cost.
- Ignoring vendor lock-in. A Lambda handler wired to API Gateway, DynamoDB streams, and Step Functions is deeply coupled to AWS. That may be an acceptable trade — but make it consciously. If portability matters, favor container-based serverless like Cloud Run and keep provider-specific logic behind interfaces.
- The distributed monolith. Splitting a system into dozens of tightly coupled functions that must all deploy together gives you the operational complexity of microservices with none of the independence. Boundaries should follow business capabilities, not code files.
- Underestimating cold starts in the critical path. Teams often discover cold-start latency in production, on the login endpoint, during a demo. Test it early under realistic conditions.
Explore Further