5 min read

Serverless Functions in Practice: What Works and What Doesn’t

Serverless Functions in Practice: What Works and What Doesn’t
Photo by Erik Mclean / Unsplash

What Serverless Really Means

Serverless functions are a way of running code in the cloud without managing the servers underneath. The term is misleading—servers are still there—but the provider takes care of provisioning, patching, scaling, and retiring them. As a developer, you supply a function, the platform executes it, and then releases the resources when it’s done. This removes the burden of capacity planning and maintaining idle infrastructure.

The model took off in the mid-2010s with AWS Lambda in 2014. It fit the rise of event-driven systems perfectly, and soon Microsoft and Google followed with Azure Functions and Google Cloud Functions. Today nearly every major cloud platform offers some version. The appeal is obvious: instead of managing an always-on backend, you focus only on the logic of “what should happen when an event occurs.”

A few properties define how serverless functions behave. They are triggered by events—an HTTP request, a file upload, a queue message, or a scheduled timer. Each invocation runs in an isolated container, with no guarantee that memory or state will survive between calls, so functions must be written as stateless units of work. Scaling is automatic: the platform can launch thousands of instances in seconds if demand requires it. Billing is granular—you pay only for the milliseconds your code is running, not for idle servers.

These traits make serverless functions appealing in recurring scenarios. They often back lightweight APIs, where each function corresponds to a single endpoint. They handle data processing, such as transforming an image immediately after upload or streaming sensor logs through a pipeline. They serve as automation glue, reacting to cloud events or scheduled jobs without the need for a persistent scheduler. They also fit integrations: a function that sleeps until a webhook arrives, then transforms the event and forwards it, is cheap and effective. Finally, because they cost nothing while idle yet scale instantly, they’re ideal for prototypes or workloads with unpredictable traffic.

The Hidden Pitfalls

The simplicity of serverless comes with sharp edges. The first is the “cold start.” When a function has been idle, the platform must spin up a fresh runtime before executing it. That provisioning delay is most visible on the first request and gets worse with large packages or private network attachments. Mitigations exist—smaller bundles, provisioned concurrency on AWS, or function “warming”—but cold starts remain a fundamental trade-off.

Another challenge is the execution environment itself. Functions are stateless and short-lived. You can write to a temporary filesystem during one run, but nothing persists across invocations. Memory may linger if a container is reused, but it is unreliable. This volatility is especially painful with database connections: each instance might open a new connection, and under load that can overwhelm the database. Managed pooling layers such as RDS Proxy or Azure’s connection pooling aren’t optional—they prevent your own scaling from becoming a self-inflicted denial of service.

Execution time is capped. AWS Lambda allows at most fifteen minutes, while Azure Functions permit up to an hour depending on the plan. Anything longer must be split and orchestrated with services like Step Functions or Durable Functions. Packaging adds another constraint: dependency-heavy applications, especially those with ML libraries, often hit the size limits of deployment bundles.

Scaling itself is both the strength and the risk. The platform happily spawns thousands of instances in parallel. Unless you configure concurrency limits and backpressure, downstream services can collapse under that sudden load.

Finally, security and observability are often weaker than expected. Teams frequently give functions broad IAM roles because least-privilege feels tedious. Out-of-the-box metrics show invocation counts and durations but not cold start latency, retry causes, or fan-out failures. Without structured logging, distributed tracing, and cold-start markers, diagnosing production issues is guesswork.

Avoid Self-Inflicted Outages

One of the most common mistakes is chaining functions directly. A Lambda calling another Lambda synchronously multiplies cold starts, couples failures, and obscures visibility. If one function needs work from another, send a message through SQS, EventBridge, or Step Functions. Those services provide retries, timeouts, and state you can inspect, rather than a daisy chain that fails unpredictably.

Another trap is creating loops by accident. A Lambda that writes to S3 while another Lambda subscribes to the same bucket can trigger itself endlessly. Teams sometimes do this intentionally, hoping to build pipelines, but the result is duplicate processing and runaway costs. The fix is isolation: separate input and output buckets, use event filters on prefixes or metadata, and add idempotency keys so replays are harmless. Always back this with deduplication logic and a dead-letter queue for poison messages.

Serverless is also the wrong tool for sustained heavy lifting. Long-running transforms, CPU-intensive jobs, or chatty loops over databases fit better on Fargate, Batch, or Kubernetes, where you can control concurrency and reuse connections. Functions are meant for bursts and glue, not pipelines that need pinned CPUs, large disks, or thousands of sockets.

Never assume warmth or connectivity. Package only what you need, initialize lazily, and avoid opening new DB connections per invocation. Secrets should live in Key Vault or Secrets Manager, not in environment variables or code. Handlers must be idempotent because retries are the default and events can arrive out of order.

And never let auto-scaling run wild. Cap concurrency, throttle at the edge, and treat every dependency as scarce. Observability is not optional—emit structured logs with trace IDs and cold-start markers so you can see where time goes when failures appear at scale.

Where Serverless Delivers

Serverless works best in narrow, well-defined cases where events are sporadic and need isolated handling. Lightweight API backends are the most obvious. Rather than deploying a full web server, each endpoint is a function invoked through an API gateway. This fits mobile apps, internal tools, or services that see irregular traffic. The cost of running at zero until demand arrives is often decisive.

Data processing is another sweet spot. A file upload to S3 can trigger a function that resizes an image, watermarks it, and publishes it to a CDN. Logs or IoT sensor events can fan out to multiple functions that filter, transform, or route data. Because invocations are short-lived and naturally parallel, throughput grows with the size of the incoming batch.

Automation is a natural use as well. Functions are perfect for running compliance checks when new resources are provisioned, cleaning up temporary files on a schedule, or sending notifications in response to system events. They replace glue code that used to require always-on servers.

Integrations with SaaS platforms are another strong fit. Most SaaS products emit webhooks or events, and a function provides a stateless, low-cost way to catch those and translate them into your internal format. Instead of maintaining a dedicated integration service, you deploy a function that wakes up only when needed.

Finally, the economics make sense for prototyping and spiky workloads. A side project can launch without infrastructure overhead, and a retail site can scale to holiday peaks then drop back to zero without cost. In both cases, serverless is aligned with unpredictable load.

When to Choose Serverless

Serverless is not a universal answer. It thrives where workloads are bursty, event-driven, and stateless. It struggles with heavy compute, long-running jobs, and workloads that need predictable latency. Knowing the boundaries is what separates successful serverless adoption from systems that drown in their own abstractions.