ESC
Type to search guides, tutorials, and reference documentation.
← Back to all categories
📱

Mobile Engineering

What makes mobile different from web and server work: irreversible releases, old versions in the wild, offline and sync, constrained resources, permissions, and the release engineering that keeps a shipped app recoverable.

Mobile engineering differs from web and backend work in one decisive way: you cannot take a release back. A bad server deploy is reverted in minutes. A bad app release is reviewed, re-submitted, re-downloaded, and then still not installed by a meaningful share of users for weeks. Every serious mobile architectural decision — feature flags, forced upgrade, offline behaviour, schema versioning — is downstream of that single constraint.

What makes mobile different

  • Releases are slow and one-way. Store review sits between you and your users, and some users never update. Assume old versions of your app will be talking to your servers indefinitely.
  • Connectivity is not a given. Not just offline — the harder case is connected-but-useless: a captive portal, a stalled request, a connection that drops mid-upload.
  • Resources are contended and metered. Memory pressure kills your process without warning. Battery and data usage are visible to the user and attributed to your app by name.
  • The operating system is in charge. It decides when you may run in the background, when it terminates you, and what you may access. Permissions can be revoked at any time, including while your app is running.
  • The UI must survive process death. A user switching apps and returning may be resuming a fresh process that must restore where they were.

Everything below follows from these five facts.

Native versus cross-platform

This decision is usually made too early and revisited too rarely. The honest framing is about where you want your costs.

ApproachStrengthsCosts
Fully native per platformImmediate access to new OS capabilities; platform-idiomatic behaviour; best tooling for profiling and debuggingTwo implementations, two sets of bugs, two review cycles; hardest to keep feature parity
Cross-platform UI frameworkOne product implementation; consistent behaviour; smaller team can cover both platformsA dependency between you and the OS; native modules still needed at the edges; platform conventions must be re-created
Shared logic, native UIBusiness rules, models, and networking written once; each platform keeps idiomatic UI and full native accessMore build-system complexity; the sharing boundary needs discipline to stay clean
Web wrapped in a shellFastest to ship; updates without store review for the web contentWeakest offline story; noticeable difference from native behaviour; limited hardware access

The decision criterion that holds up: how much of your product's value is in platform-specific capability? An app whose core is deep camera, sensor, background, or accessibility integration pays a compounding tax on any abstraction layer. An app that is mostly content, forms, and lists — with occasional native touches — usually does not.

Application architecture

The specific pattern names vary by platform and era; the properties they are all reaching for do not:

  • Unidirectional data flow. State flows down, events flow up. The alternative — views mutating each other directly — becomes untraceable once there are more than a handful of screens.
  • UI as a function of state. Given the same state, the screen looks the same. This is what makes process-death restoration and screenshot testing tractable.
  • Business logic outside the view layer. View controllers and activities have lifecycles bound to the OS, and logic that lives inside them cannot be tested without one.
  • Navigation as data. Representing the navigation stack as serialisable state — rather than as a sequence of imperative calls — is what makes deep links, restoration, and back-behaviour correct rather than approximate.

Modularisation matters more on mobile than its reputation suggests, because build times dominate the inner loop. Splitting into modules with explicit dependencies allows incremental builds to skip work. The trap is a module graph where everything depends on one large shared module, which reintroduces full rebuilds while adding ceremony.

Offline-first and synchronisation

"Offline support" bolted on late is a rewrite. Designed in, it is mostly a single decision: the local database is the source of truth the UI reads from, and the network is a background process that reconciles it with the server. The UI never waits on the network to show something it already has.

Writes then need a durable outbound queue: the mutation is applied locally, recorded as pending, and retried until the server accepts it. This requires the same idempotency discipline as any distributed system — each queued mutation carries a stable client-generated identifier so a retry after an ambiguous timeout does not create a duplicate.

Conflict resolution has to be an explicit product decision. Last-write-wins is simple and silently destroys the loser's work. Field-level merge preserves more but only makes sense for independent fields. Surfacing the conflict to the user is the most honest and the most expensive. There is no default that is correct for all data — choose per entity, and write the choice down.

Finally, version the local schema from the first release. The device carries its database across app upgrades, and an app that cannot migrate its own local data has only two options on upgrade: crash, or delete the user's data. Both have shipped.

The networking layer

A shared client layer is worth building once, because these concerns are identical for every call: timeouts, retry with backoff and jitter, authentication token refresh with concurrent-request coalescing, request deduplication, cancellation when the screen goes away, and consistent error mapping.

The token refresh case deserves specific attention: when a token expires, several in-flight requests will fail at once, and a naive implementation fires several refreshes concurrently, which can invalidate each other and log the user out. One refresh, with the other callers waiting on it, is the correct shape.

Payload size is a user-visible cost on metered connections. Prefer endpoints shaped for the screen over generic ones that require the client to fetch and discard. For anything large, use the platform's background transfer facilities rather than holding a request open in a foreground process that the OS may terminate.

Performance and resources

Startup is the most consequential metric because every user pays it. The usual causes of a slow start are work done eagerly at launch that could be deferred or done lazily: initialising every subsystem, reading large files synchronously, or making network calls before the first screen can render.

Frame budget determines whether scrolling feels right. The device refreshes on a fixed interval, and any work on the main thread that overruns that interval drops a frame. Common causes are layout work in list item binding, image decoding on the main thread, and synchronous disk or database reads during scroll.

Memory is a correctness concern, not only a performance one, because the OS terminates the largest background consumer under pressure. Images are usually the dominant term: decode to the size actually displayed rather than the source resolution.

App size affects install and update conversion, particularly on limited devices and metered connections. It grows by accumulation — bundled assets for every density, unused dependency code, embedded fonts and localisations — so it needs a budget enforced in CI for the same reason web bundles do.

Release engineering

This is where mobile earns its reputation for discipline, precisely because of the irreversibility described at the top.

  • Feature flags and remote configuration. The only way to turn a feature off in a shipped binary. A kill switch for anything risky is the mobile equivalent of a rollback.
  • Staged rollout. Release to a fraction of users, watch crash-free rate and key funnels, and expand only if they hold. Halting a staged rollout is far cheaper than shipping a fix.
  • Crash and error reporting with symbolication, wired up before you need it, with the build metadata needed to attribute a crash to a release.
  • Forced and recommended upgrade paths. A server-driven minimum supported version, designed and tested early, so that when you genuinely need everyone off a broken build you are not shipping that mechanism under pressure.
  • Backward compatibility on the server. Your API contract must hold for every app version still in use, which makes the API evolution discipline described in API development a hard requirement rather than a nicety.

Security on a device you do not control

Start from the assumption that the binary will be inspected and the device may be compromised. That leads to a short list of durable rules: no secret that must stay secret belongs in the app bundle, because anything shipped to a device is readable; store credentials and tokens in the platform's protected keystore rather than in general application storage; treat all client-side validation as a user-experience feature and enforce every rule again on the server; and be deliberate about what is written to logs and to backups.

Permissions are both a security and a trust surface. Request them at the moment the user is trying to do the thing that needs them, with context, rather than at first launch — a permission denied at launch is often denied permanently, and a permission can be revoked later, so every access needs a working path for "not granted".

Common failure modes

  • Assuming connectivity. Spinners with no timeout and no offline path.
  • Main-thread work. Disk, database, decoding, or JSON parsing on the UI thread, surfacing as jank and unresponsiveness.
  • No local schema versioning. Upgrade either crashes or wipes the user's data.
  • No kill switch. A bad feature cannot be disabled without a store release.
  • Concurrent token refresh. Several simultaneous refreshes race and sign the user out.
  • Ignoring process death. Tested only by returning to a warm app, so restoration is broken for the users who hit it.
  • Permission prompts at launch. Asked without context, denied by default, and then not handled.
  • An API that assumes the newest client. Breaking older versions that cannot be upgraded on demand.

Choosing an approach

Go fully native when platform capability is the product, when performance or hardware integration is the differentiator, or when you have the team to maintain two codebases properly. Choose a cross-platform UI framework when feature parity and delivery speed across both platforms matter more than platform-idiomatic detail, and you accept a dependency between your product and the OS. Choose shared-logic-with-native-UI when the domain rules are substantial and worth writing once but the interface should feel native. Choose a web-based shell when the content is genuinely web content and offline is not part of the value.

And consider not shipping an app at all. If the experience does not need offline capability, background execution, hardware access, or push notifications, a well-built responsive web experience avoids store review, reaches everyone immediately, and can be updated the same day. An app is a distribution channel with real ongoing costs; it should be chosen for capabilities that justify them.

01

API Development

How to design, evolve, secure, and operate an HTTP API: protocol styles, contract design, error and pagination models, idempotency, versioning, rate limiting, and the failure modes that break clients.

→
02

Backend Development

What backend engineering is really about: request lifecycle, where state lives, concurrency and connection pools, transactions, background work, caching, failure handling, and the operational habits that keep a service correct under load.

→
03

Frontend Architecture

Rendering strategies, state ownership, data fetching, component boundaries, performance, and accessibility — the architectural decisions that determine whether a frontend stays maintainable as it grows.

→
04

Mobile Engineering

Cross-platform development, app security, certificate pinning, and mobile CI/CD.

→
05

Mobile Offline-First Architecture

Build mobile applications that work reliably without network connectivity. Covers offline data storage, sync strategies, conflict resolution, queue-based operations, optimistic UI, and the patterns that make offline-first feel seamless rather than broken.

→
06

Mobile App Architecture Patterns: Choosing the Right Foundation

Evaluate mobile architecture patterns — MVC, MVVM, MVI, Clean Architecture — and choose the right one for your team size and app complexity. Covers state management, dependency injection, navigation patterns, and the architecture decisions that prevent complete rewrites.

→
07

Mobile CI CD Pipeline

Production-grade guide to mobile ci cd pipeline covering architecture patterns, implementation strategies, testing approaches, and operational best practices for enterprise engineering teams.

→