Serverless platforms take the servers away from the developer, and with them the feeling that anything can break. A function is a small piece of code, an event arrives, a result leaves. Because the platform handles scaling, patching and placement, teams often write the happy path, wrap it in one broad try-catch block and move on. This commentary argues that this is the wrong place to be relaxed. A function runs in an environment the author does not control, triggered by systems that deliver events more than once, calling dependencies that fail without warning. How it behaves when something goes wrong is not a detail of the implementation. It is the part of the design that decides whether the function can be trusted at scale, and it is underappreciated precisely because the platform hides the machinery that fails.
Which failures does a function actually face?
A function can fail in several distinct ways, and they do not all look alike from the inside. The familiar one is an exception thrown by your own code, which a catch block can see and handle. Others never reach your code at all. A timeout ends the invocation from outside, and so does exceeding the memory limit, so no catch block runs and no cleanup happens. Throttling can prevent the function from starting. A malformed event can fail validation before any business logic runs, and a downstream database or API can be slow, unavailable or return something unexpected. Each of these is reported through a different channel, some in your logs and some only in platform metrics. A team that relies on try-catch alone sees only a fraction of its failures, so alarms on platform-level signals are as necessary as the handling code.
Retries are a feature until the handler is not idempotent
Most event sources deliver at least once, and many retry automatically when a function reports failure. Asynchronous invocations are retried by the platform, queue triggers make a message visible again, and stream triggers keep retrying the same batch of records. This is helpful for transient faults and dangerous for handlers with side effects. If a function charges a card, sends an email and then fails on the last step, the retry repeats the first two. The remedy is to make handlers idempotent, for example by recording an idempotency key before acting or by using conditional writes, so that a repeated event produces the same outcome as the first. Messages that can never succeed need a dead-letter queue or failure destination, or a single poison message will block a queue or a stream shard indefinitely.
Errors that say too much
The other half of error handling is what the function tells the outside world. A raw exception message or stack trace returned to a caller can reveal table names, file paths, library versions and the shape of internal services, which is useful reconnaissance for an attacker. The safer pattern is to map internal failures to a small set of stable error codes with generic messages, and to log the detail on the server side together with a correlation identifier the caller can quote when they ask for help. Logs deserve the same care, since error paths are exactly where request bodies, tokens and personal data tend to end up in plain text. Structured logging with deliberate redaction turns the error path from a liability into the most useful diagnostic record the system has.
Why it matters
Serverless makes it cheap to run a function a million times, which also makes it cheap to repeat a mistake a million times. Weak error handling shows up as duplicated charges, stuck queues, silent data loss and leaked internals, and it usually shows up late, under load, when the cost of finding it is highest. Treating failure as a first-class part of the design, with alarms on platform signals, idempotent handlers, dead-letter paths and disciplined error responses, is what separates a function that merely works from one that can be left running unattended.
A serverless function without a failure plan is a retry loop waiting for its first bad message.