BT

Facilitating the Spread of Knowledge and Innovation in Professional Software Development

Write for InfoQ

Topics

Choose your language

InfoQ Homepage Articles The App Is Just the Tip: What It Takes to Build Mobile Infrastructure at Scale

The App Is Just the Tip: What It Takes to Build Mobile Infrastructure at Scale

Listen to this article -  0:00

Key Takeaways

  • Moving response aggregation, feature-flag evaluation, and error handling off the mobile client into a server-side gateway cut product-detail render time roughly in half and reduced per-screen network calls from five-to-seven down to one.
  • A backend-driven UI lets teams change what users see by editing gateway configuration instead of shipping an app release, dropping time from code-complete to majority rollout from one-to-two weeks to under four hours.
  • Server-driven UI (SDUI) is not a single decision: full SDUI, hybrid, and data-only each trade adoption cost against product velocity, with a hybrid model fitting an established app with bespoke components.
  • Classifying every downstream call per screen as required, preferred, or optional in a dependency-criticality matrix co-owned by engineering and product decides degradation behavior before an incident rather than during one.
  • A live, high-traffic system can be migrated without a faithful staging environment by running shadow mode, comparing responses, then graduating traffic through canary stages with automated rollback tied to error-rate thresholds.

I spent six years at a large e-commerce platform, the kind with tens of millions of monthly active users, hundreds of backend services, and an Android and iOS app that had to hold it all together. For the first few years, I was an Android developer, building features that users could see and touch. Then I crossed over to the other side. I stopped writing mobile code and started building the backend infrastructure that every mobile developer on the team depended on: a gateway layer that sat between the mobile apps and everything else.

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.

The problem with smart apps

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.

The gateway pattern: one interface, hundreds of services

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.

Measured impact after full migration:

Metric Before After Change
Product detail screen render time (p50, mid-range Android) 2.8s 1.4s −50%
Network calls per screen load (product detail) 5–7 1 −80%
Client-side payload processing time 900ms <120ms −87%
App cold start time 4.1s 2.9s −29%
APK size growth rate ~2 MB/quarter ~0.8 MB/quarter −60%
Crash rate (monthly) 0.41% 0.26% −37%

Trade-offs of the gateway pattern

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.

Backend-driven UI: decoupling velocity from the app store

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.

Release velocity, before and after:

Metric Before gateway After gateway
Time from code-complete to 50% user rollout 10–14 days <4 hours
Product experiments launched per month 3–5 15–25
Rollback time for a broken experiment 2–5 days (new release) <10 minutes (config change)
Percentage of changes requiring app release ~80% ~15%
App versions active in production (trailing 30 days) 8–12 3–4

Once you move logic to the backend, the app becomes a canvas. You control what gets painted on it.

Backend-driven UI: pattern selection

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.

Migrating a live system with no dry run

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:

  1. Shadow mode. The gateway ran alongside the existing direct calls. Both paths executed, but the app only used the response from the old path. We compared the two responses on the server, logging discrepancies.
  2. Canary rollout. Once discrepancy rates fell below 0.1%, we routed 1% of traffic to the new path. We monitored screen load time, error rate, and downstream service call patterns for at least 48 hours.
  3. Graduated rollout. We increased to 5%, 25%, 50%, and 100% over one to two weeks per screen, with automated rollback triggers. If the error rate for a screen exceeded the baseline by more than 0.5 percentage points, traffic automatically reverted to the old path.
  4. Old path removal. After four weeks at 100% with stable metrics, we deleted the old client-side integration code.

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.

Making the system reliable at scale

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:

  • Required: The screen cannot render without this data. If the call fails, the gateway returns an error to the app (e.g., product price and availability on the product detail screen).
  • Preferred: The screen degrades without this data, but a fallback exists. The gateway serves cached data or a simplified response. (Example: personalized recommendations fall back to popularity-based recommendations.)
  • Optional: The screen is complete without this data. If the call fails, the section is omitted. (Example: "customers also bought a carousel.)

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.

What this work requires

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."

Practical takeaways

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:

  • Audit where your logic lives. Map which decisions are being made in the app versus on the server. If the app is doing data aggregation, feature flag evaluation, or complex conditional rendering, those are candidates for extraction to a gateway layer.
  • Start with the highest-churn screens. The screens that change most frequently in response to product experiments are the ones where backend-driven UI will pay off fastest. Migrate those first.
  • Invest in observability before you migrate anything. You need per-screen, per-flow metrics for latency, error rates, and payload sizes. Without them, you are flying blind during migration.
  • Define degradation behavior explicitly and collaboratively. Build a dependency-criticality matrix for product and design. Classify each downstream call as required, preferred, or optional. Do not leave this to the judgment of whoever is on call at 3 a.m.
  • Choose your SDUI depth deliberately. Full server-driven rendering, hybrid, or data-only — each has different adoption costs and different payoffs. Match the approach to your team's maturity and your product's iteration cadence.
  • Treat the gateway as a product, not a utility. It has users (your mobile developers), requirements (reliability, performance, developer experience), and needs investment over time. If you starve it, the costs show up in app quality.
  • Staff it with people who understand both sides. The best gateway engineers are the ones who have worked on mobile and backend, because they can reason about constraints on both sides of the boundary. If you cannot find people with that dual background, invest in cross-training.

Conclusion

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.

About the Author

Rate this Article

Adoption
Style

BT