Article: The App Is Just the Tip: What It Takes to Build Mobile Infrastructure at Scale
What determines whether your app feels fast, ships often, and stays stable at scale is not the app itself. It is what sits behind it. This article is about that layer: what it does, why it exists, what it takes to design it correctly, and what happens when you try to migrate a live, high-traffic pr

InfoQ Homepage Articles The App Is Just the Tip: What It Takes to Build Mobile Infrastructure at Scale
That transition changed how I think about mobile development entirely. Most conversations about mobile apps focus on the UI, the framework, and the design system. But the thing that determines whether your app feels fast, ships often, and stays stable at scale is not the app itself. It is what sits behind it.
This article is about that layer: what it does, why it exists, what it takes to design it correctly, and what happens when you try to migrate a live, high-traffic product onto it without a safety net.
Mobile apps run on the user's device. That sounds obvious, but the implications are easy to underestimate when you're designing a system. The device has a limited battery, limited memory, and a network connection that is often unstable, slow, or both. Every piece of logic you push into the app competes for those resources.
When your app is small, this is manageable. When your app serves millions of users and interacts with hundreds of backend services, each owned by a different team, the weight of client-side logic can start to compound. The app gets slower. Crash rates climb. Battery drain increases. And every fix requires a new release, which means going through app store review, waiting for rollout, and hoping users bother to update.
To make this concrete, here is what our architecture looked like before we built the gateway:
Figure 1. Direct client-to-service communication before introducing the mobile gateway. [Source: Diagram created by the author with ChatGPT assistance.]
The mobile app knew about every downstream service. It made multiple network calls per screen, merged responses client-side, evaluated feature flags locally, and handled each service's error modes individually. A single product detail screen required calls to five or six services, and the app contained the logic to stitch those responses together into a renderable view.
The costs were measurable. On mid-range Android devices – which represented roughly 60% of our user base – the product detail screen took 2.8 seconds to render. Of that, approximately 1.1 seconds was network time (multiple sequential and parallel calls), and 0.9 seconds was client-side data transformation. The app's cold start time had crept past 4 seconds. APK size grew by roughly 2 MB per quarter, driven partly by the accumulation of client-side service integration code.
When a mobile app is primarily a client to remote services, it should own as little backend orchestration as possible. The more of that logic lives on the client, the more fragile the app becomes.
We built a proxy layer that sat between all mobile clients and the rest of the platform's backend. The system had two primary responsibilities.
First, it prepared data for mobile developers so they could consume it without having to understand the complexity beneath it. A large marketplace has a sprawling backend: inventory systems, pricing engines, delivery logistics, recommendation services, payment processing, and many more. A separate team owns each, sometimes structured as a separate business unit with its own codebase and deployment pipeline.
A mobile developer building a product card screen should not need to know how to call five different services, merge their responses, handle partial failures, and transform the result into something the UI can render. The gateway did that for them.
Second, the gateway absorbed logic that would otherwise have lived in the app. Data transformations, feature flag evaluation, response aggregation, and error handling for downstream failures – all of this moved to the server.
Figure 2. Gateway-mediated architecture, with aggregation and orchestration moved to the server. [Source: Diagram created by the author with ChatGPT assistance.]
The app now makes a single request per screen. The gateway resolved which backend services to call, called them in the optimal order (parallelizing where possible), composed the response, applied feature flags and experiment variants, and returned a pre-shaped payload that the app could render with minimal processing.
This architecture is not free. Introducing a gateway creates a new critical dependency, adds a network hop (typically 5–15ms within the same data center), and requires a team to own and operate it.
Latency. The gateway adds a hop, but it removes multiple sequential client-to-service calls and replaces them with a single client-to-gateway call followed by parallel server-to-server calls. Server-to-server calls run over low-latency datacenter links rather than mobile networks, so the net latency is almost always lower. In our case, the additional gateway hop was far outweighed by the elimination of 4–6 mobile network round-trip.
Operational complexity. You now have a service that must handle all mobile traffic. This demands investment in redundancy, autoscaling, and circuit breakers from day one. We ran the gateway across multiple availability zones with a target of 99.99% uptime – stricter than any individual downstream service – because a gateway outage means a total app outage.
Coupling risk. A gateway can become a monolith if you are not careful. We avoided this by making the gateway configurable rather than code-heavy. Screen definitions were declared in configuration files, not in application code. Adding a new screen or modifying an existing one did not require deploying new gateway code; it required updating a configuration that specified which services to call, how to compose their responses, and what fallback behavior to use.
Team boundaries. The gateway team becomes a dependency for every mobile feature. We mitigated this by building a self-service model: mobile teams could define their own screen configurations, and the gateway would execute them. The gateway team owned the runtime and the framework; product teams owned their screen definitions.
My path into this work was not typical. I started as an Android developer and spent years building features in the app. Then I moved into Android infrastructure, improving the internal tooling used by other mobile developers. That gave me visibility into the pain points: where developers were struggling, where the app was doing too much, and where the boundary between client and server was drawn poorly. When I eventually crossed into backend work to design the gateway, I carried all of that mobile context with me. It is hard to build a good mobile backend abstraction without first having lived on the mobile side.
Once we moved the bulk of the logic into the gateway, something important happened: we stopped depending on mobile app releases for most product changes.
The release cycle is one of the most underappreciated bottlenecks in mobile product development. Shipping a new version of an app means going through the Google Play or App Store review process. It means a gradual rollout over days or weeks. It means that even after rollout completes, a significant percentage of your user base will still be running older versions, because people do not reliably update their apps. Research consistently shows that users dislike frequent updates; each one costs bandwidth and time, and too many of them erode trust.
For the business, this creates a painful tension. Product teams want to iterate quickly: run experiments, tweak layouts, adjust flows based on data. But every change that lives in app code requires a release, and releases are slow. A feature that takes two days to build can take two weeks to reach most users.
Our gateway architecture resolved this by making the app a rendering layer rather than a decision-making layer. The server described what the app should display – which components to render, in what order, with what data – and the app displayed it. We could change what users saw by updating the gateway configuration without touching app code, bypassing the store, and without the user noticing anything.
If we want to change product behavior without releasing a new app, the business logic that controls that behavior needs to live on the server, not in the client.
Once you move logic to the backend, the app becomes a canvas. You control what gets painted on it.
There are several approaches to backend-driven UI, and the right choice depends on your constraints:
Full server-driven rendering (SDUI). The server sends a complete component tree: which components to render, their properties, and their ordering. The app is a generic renderer. This gives maximum server-side control but requires a mature component library on the client and careful versioning – old app versions need to handle component types gracefully that they do not recognize.
Hybrid approach. The server controls data and layout configuration (which sections appear, in what order, with what data), but the app retains ownership of how individual components render. This is easier to adopt incrementally and works well when you have an established app with complex, bespoke UI components.
Data-only gateway. The server aggregates and shapes data, but the app retains all layout and rendering decisions. This captures the performance benefits (fewer network calls, thinner payloads) without the product velocity benefits of server-controlled layout.
We chose the hybrid approach. Full SDUI offered the most flexibility but would have required a generic rendering system, strict schema versioning, and robust compatibility handling across app versions. A data-only gateway was simpler, but it would not have solved the release-cycle bottleneck for layout and experiment changes.
The hybrid model gave us a middle ground: the gateway controlled screen structure – sections, ordering, visibility rules, and experiment variants – while the app retained ownership of rendering. The cost was an additional server-client contract that had to be versioned, validated, and kept backward-compatible with older app versions. Adding a new section type still required an app release; only existing sections could be reordered, hidden, or reconfigured entirely from the server.
We designed the gateway from scratch. But the hard part was not the design. It was the migration.
The platform was a live, high-traffic application serving tens of millions of users. The existing architecture had the mobile apps talking directly to various backend services, with significant logic residing on the client. We needed to redirect all of that traffic through the new gateway, re-implement the data preparation logic server-side, and do it without users noticing a difference. No staging environment faithfully replicates production traffic at that scale. You cannot simulate millions of real users, real devices, and real networks. You test what you can, you build in observability, and then you do it live.
The migration was incremental. We moved screens and flows one at a time, following this process:
The trickiest part was handling the long tail of edge cases that only surface at scale. A service that returns slightly different data under load. A timeout that is acceptable at the 95th percentile but catastrophic at the 99.9th. A downstream team that deploys a change without telling you, and suddenly, your gateway is sending malformed data to a fraction of your users. These are the problems that no amount of unit testing prepares you for. You learn to solve them through instrumentation, gradual rollout, and a healthy dose of paranoia.
We migrated the entire app to the new architecture while it was running. Since production conditions could not be fully replicated in advance, we relied on a carefully staged migration strategy.
After the migration, my focus shifted to resilience. A gateway that sits between your entire mobile user base and every backend service is, by definition, a single point of failure. If it goes down, the app goes dark.
Making it reliable enough to earn that position required sustained, deliberate work.
The basics are well-known: redundancy, circuit breakers, timeouts, retries with backoff, and graceful degradation when downstream services fail. What I found more interesting were the less obvious challenges.
Defining "degraded" at the product level. If a recommendation service is down, should the product page show a blank recommendations section, a cached version, or no recommendations section at all? The technically correct answer depends on the product context. A missing section might appear to be a bug. A stale section might show irrelevant items. Removing the section entirely might be the cleanest option, but the product team might object because it reduces engagement. These decisions cannot be made solely by the infrastructure team. They require tight collaboration among engineering, product, and design, and need to be codified in a configuration that the gateway can act on automatically.
We built a dependency criticality matrix that classified each downstream service call per screen as required, preferred, or optional:
This matrix was co-owned by engineering and product. Every new screen definition required filling it in. It eliminated ambiguity during incidents and allowed on-call engineers to focus on restoring service rather than making product judgment calls at 3 a.m.
Understanding actual capacity. Load testing helps, but production traffic patterns are not replicated by synthetic load. Real users cluster around promotions, flash sales, and time-of-day peaks in ways that create hotspots you did not anticipate. We learned to instrument aggressively and to treat capacity planning as a continuous activity. We tracked p50, p95, and p99 latencies per screen, per downstream service, and per availability zone, and reviewed them weekly.
The goal is a system that is boring in production. If you are thinking about it while it is running, something has gone wrong.
Building and maintaining mobile infrastructure at this level selects for a specific set of skills for a gateway engineer. You need to understand mobile constraints deeply enough to make good trade-offs for mobile developers who will never see your code. You need to understand backend systems deeply enough to anticipate failures, manage dependencies, and reason about latency budgets across dozens of services. You need enough product sense to know which degradation modes are acceptable. And you need communication skills to work across organizational boundaries, because the services your gateway depends on are owned by teams with their own priorities, release schedules, and definitions of "working correctly."
If you are building or operating a mobile application that has outgrown its original architecture, here is what I would recommend based on this experience:
The mobile app is what users see. But the infrastructure behind it has an effect on how fast it loads, how often it crashes, how quickly the product team can iterate, and how gracefully it handles failure. That layer is invisible to users and often invisible to the broader engineering organization. Building it well is among the most technically demanding and business-critical work in mobile product development.
It is also, in my experience, some of the most satisfying. You are building something that every other engineer on the team depends on. When it works, everyone moves faster. When it does not, everyone feels it. That kind of accountability is a privilege, and it keeps you honest.
Find the bugs your tests miss. Antithesis uses deterministic simulation to explore thousands of failure scenarios, uncovering hard-to-reproduce bugs before they reach production. See how it works.