Production Bugs Don’t Usually Come From Bad Code. They Come From Real Users.

When I first started working on a production mobile application built with .NET MAUI, I assumed most bugs would come from incorrect business logic or missed edge cases in the code.

I couldn’t have been more wrong.

Over the past few months, I’ve investigated production crashes ranging from networking failures and memory issues to race conditions, platform-specific behaviour, and lifecycle bugs. What stood out wasn’t the variety of issues, it was the pattern behind them.

Most production bugs weren’t caused because the code was “wrong.” They happened because real users don’t interact with apps the way developers do.

A user rotates their phone while an image is still loading. Another loses internet halfway through an API response. Someone swipes to delete an item and immediately backgrounds the app. A page disappears while a network request is still running. These are the kinds of scenarios that are hard to reproduce during development but happen every day in production.

One lesson that came up repeatedly was that mobile networking is inherently unreliable. A request reaching the server doesn’t necessarily mean the client will receive the response successfully. We saw cases where the server returned a successful response, but Android dropped the connection while reading the response body. The fix wasn’t simply adding retries, it was making sure the entire request lifecycle was resilient to transient failures.

Another recurring theme was concurrency.

Many crashes weren’t logic errors at all. They were timing issues. Background tasks updated collections while the UI was reading them. Multiple threads attempted to write to the database at the same time. Operations completed after users had already navigated away from a screen.

These bugs don’t appear because the code fails every time, they appear because it fails once under just the right conditions.

Memory management was another eye-opener.

Modern phones have plenty of RAM, yet loading oversized images or forgetting to unregister event listeners was enough to cause crashes after extended app usage. Sometimes the solution wasn’t a complex refactor. It was simply cleaning up resources when a page disappeared or loading an image at the size it would actually be displayed.

Perhaps the biggest surprise, though, came from our monitoring.

Thousands of events being reported weren’t actual application bugs. Session expirations, disabled location permissions, temporary HTTP 503 responses, and short-lived network interruptions were all ending up in crash reports. Once those expected scenarios were treated as warnings instead of failures, the monitoring dashboard became far more useful. The real issues were finally visible.

It changed the way I think about observability. Good monitoring isn’t about collecting more data, it’s about collecting meaningful data.

Working on these issues also reinforced something that every cross-platform developer eventually discovers: cross-platform doesn’t mean platform-identical. Android and iOS have different lifecycle behaviours, threading models, and memory constraints. Sharing code is incredibly valuable, but understanding how each platform behaves is just as important.

Looking back, most fixes were surprisingly small. A cancellation token. A null check. Moving a UI update onto the main thread. Protecting a shared resource with a semaphore. Validating data before processing it.

Tiny changes, but each one prevented crashes that affected hundreds or even thousands of users.

The biggest takeaway from working on production issues is that building mobile apps isn’t just about implementing features. It’s about designing software that behaves predictably when everything around it becomes unpredictable; slow networks, interrupted sessions, background execution, device rotations, and imperfect data.

Development gets an app working. Production teaches you how to make it reliable.


And in my experience, those are two very different engineering skills.

In this article:
Share on social media: