Mobile System Design Building Blocks
The architecture, storage, networking and delivery patterns you reach for again and again — and the trade-offs that decide between them.
A working vocabulary for mobile interviews — the architecture, storage, networking, and delivery patterns you reach for again and again, and the trade-offs that decide between them.
Each section defines a building block, says when to raise it in an interview, and names the trade-off that picks between options. A [+] badge marks context added beyond the source. Diagrams are original. Every further-reading link was fetched and confirmed live on 24 August 2026.
Good architecture is a shared language, not a diagram#
Architecture does two jobs at once: it gives an app a structure that stays maintainable as it grows, and it gives a team a common vocabulary for talking about that structure. In interviews you rarely have to name a framework — you have to show you understand the principles the frameworks encode. A handful of principles carry almost every answer.
Separation of concerns#
Keep UI logic out of everything else. A UI class should only display content and react to user input; business rules and data access live elsewhere. This makes each piece testable in isolation and sidesteps a whole category of bugs tied to the UI lifecycle, because the logic no longer dies and resurrects with the screen.
Unidirectional Data Flow#
Data moves in one direction — from a source (say a database) through transformation layers to the UI. Changes travel back the other way as events the source exposes; nothing mutates data by reaching sideways into it. That single rule buys predictability (changes only happen through defined paths), fewer bugs (there are fewer places data can change), and traceability (you can follow exactly how a value reached the screen).
Reactive programming
is the usual way to implement this flow: the source publishes a stream, consumers subscribe, and updates
propagate automatically. Kotlin Flows or RxJava on Android; Combine or RxSwift on iOS; Kotlin
suspend
functions and Swift async/await
for one-off async work. Callbacks and event buses can work for narrow cases and event buses do reduce coupling,
but streams keep state management far easier to reason about.
Immutable data and a single source of truth#
Immutable state — data that can't change after creation — removes a class of concurrency bugs and makes behavior predictable, which matters on mobile where several threads often touch the same data. Reserve mutable structures for rare performance-critical spots (an image-processing module, say) and isolate them from the app's main state.
Pair immutability with a Single Source of Truth (SSOT): each type of data has exactly one owner with exclusive authority to change it. The owner receives change events, applies business rules, and re-exposes updated immutable state. A local database is often the SSOT for offline business data; a UI state holder is the SSOT for its screen's display state.
Dependency Injection#
As components multiply, so do the dependencies between them. Prefer Dependency Injection over the Service Locator pattern: rather than a component constructing what it needs, its dependencies are handed in — usually through the constructor. That makes requirements explicit and lets you swap real implementations for test doubles. Service Locator can hide dependencies and complicate testing, which is why this book avoids it.
Throughout the book, arrows show data flow, not dependencies. Dependency Injection appears as a box sitting between architectural layers — a boundary that keeps components loosely coupled.
Decoupling, error propagation, and minimal exposure#
-
Decouple through interfaces.
Depend on an abstraction, not a concrete class, so a
MapsRepositorycan talk to aMapsProviderinterface — swapping a test double in tests and A/B-testing an in-house vs. third-party provider in production. -
Propagate errors deliberately.
The UI shows friendly messages and handles simple validation; lower layers handle business-logic and
data-source failures and pass up only what the user needs to know. Kotlin coroutines and Rx operators
(
onError) handle this natively; on platforms without such tools, aResulttype that wraps success/failure models the cases explicitly. - Expose the minimum. Each component receives only the data it needs. A conversation-list screen wants names, message previews, and timestamps — not every message in every thread. Less data means less memory, CPU, bandwidth, and battery, and simpler flows to test. Transform data at layer boundaries so each layer handles only what's relevant to it.
Two layers, sometimes three, and a repository in the middle#
The reference architecture here follows Google's guide to app architecture. Written for Android, its principles transfer cleanly to iOS. At its core sit two layers — UI and data — with an optional domain layer between them for logic that's shared or reused across screens.
UI layer#
The UI layer (also called the presentation layer) renders application data and is the primary point of interaction. It has two parts: UI elements that draw to the screen, and state holders that hold data, expose it, and run presentation logic. The state holder turns business data into UI state — a blueprint of what should be on screen. As the docs put it: if the UI is what the user sees, the UI state is what the app says users should see. Each screen gets its own state holder, wired in through dependency injection. Whether this reads as MVVM or MVI is beside the point — both express Unidirectional Data Flow, and the interview should stay on the overall design rather than the label.
Data layer#
The data layer implements the repository pattern. A repository per data type exposes data to the app, centralizes changes, resolves conflicts between sources, hides where data comes from, and holds business logic. Under each repository sit data sources, each responsible for exactly one source — a network endpoint, a local database, or a file. A remote data source talks to the backend; a local data source persists to the device, often via a database. Define repositories as interfaces so DI can swap the real implementation for a test double.
Navigation#
For most interviews, simplify navigation to a global Navigator (or Coordinator) that moves between screens and supplies each screen's dependencies — both static ones like state holders and platform services, and dynamic ones like the item ID a detail screen needs. Real apps get far messier as screens, dialogs, and deep links [+] multiply, but you rarely need to design a full navigation graph unless asked.
Duolingo moved to MVVM with Jetpack and Dagger/Hilt for DI. Notion leans on a flexible data model and unidirectional flow, which brought power and complexity to its mobile clients. If you've shipped a different architecture successfully, say so — lived experience is as valuable as any reference model.
Modules for build speed, tests for trust#
Modularization splits an app into independent building blocks. It aids maintainability and scaling, but its headline benefit is build speed: better use of build resources, less code to recompile, and parallel compilation. It isn't free — more modules mean more overhead if you're careless — so it needs planning.
The industry increasingly uses the contract module pattern: each component has at least an API module (the interface) and an implementation module (the code behind it). Features depend only on the stable API module, so changing an implementation doesn't force dependents to rebuild. The top-level app module wires real implementations in; a test module swaps test doubles in.
Airbnb restructured its iOS app into nearly 1,500 modules for team productivity. Robinhood broke up a "giant monolith" Android module into feature modules and cut build times.
A testing strategy worth stating out loud#
Testing shows that your architecture supports quality. Define your terms — teams disagree on names — then cover the pyramid:
Layer in specialized checks where they matter — accessibility, performance, memory-leak, and security tests [+] cut across the pyramid rather than replacing it. Fake the backend with OHHTTPStubs on iOS or MockWebServer on Android, and lean on the robot pattern to keep UI tests readable.
Pick storage by data shape, not by habit#
How an app persists data drives its performance and reliability. Start by naming what kind of data you're holding — each has a different lifetime and access pattern:
- User data — profile info, settings, preferences that personalize the experience.
- User files — photos, videos, documents the user creates; lost if the user deletes them.
- Business data — articles, listings, messages; re-fetchable from the backend if removed.
- Sensitive information — tokens, encryption keys; needs protected storage.
- Cache data — downloaded images, precomputed results; in memory or on disk.
- Temporary data — upload chunks, drafts, undo history; safely discarded when done.
The five options#
| Mechanism | Best for | Watch out for |
|---|---|---|
| Key-value | Non-sensitive preferences, settings, feature toggles | Large or structured data; no advanced queries; weaker security |
| Secure storage | Credentials, payment data, cryptographic keys | Encryption overhead; limited capacity; harder to test |
| Relational DB | Large structured datasets with relationships and complex queries | Setup and maintenance cost; resource use; query latency |
| Non-relational DB | Unstructured or semi-structured data, high-throughput CRUD | Limited complex queries; potential consistency gaps |
| Files | Binary blobs, note-per-file designs, custom formats | Manual integrity, concurrency, and scaling |
A decision path you can narrate#
The tree simplifies. A social app might use secure storage for tokens, a relational DB for profiles and posts, key-value for settings, and files for cached media — all at once. Netflix chose Realm for offline video metadata; Trello made its local DB the single source of truth to work offline.
Everything is a file — behind a sandbox wall#
Whatever the API, storage ultimately lands as files on disk; the options differ in how they organize and expose those files. By default, each app gets a private, sandboxed space no other app can read — basic isolation and security. That wall has a limit: on jailbroken iOS or rooted Android, the sandbox can be bypassed, which is exactly why hardware-backed secure storage matters for sensitive data even on compromised devices.
Reaching outside the sandbox#
- Device-wide storage. Android exposes shared areas — built-in "external" storage and removable media — for files meant to be shared, like photos and documents. iOS is more controlled: a Documents directory for user-visible, iCloud-backed files, and a Library directory for data the app needs but users shouldn't see directly, browsable through the Files app.
- Media and files from other apps. iOS uses PhotoKit (PHAsset/PHImageManager) to read the photo library and UIDocumentPickerViewController to pick arbitrary files; Android uses MediaStore for media and the Storage Access Framework for documents. These APIs run the permission prompts, keeping the user in control of what each app can touch.
- Sharing between your own apps. Android uses Content Providers with signature-based permissions so only apps signed with the same certificate share data; iOS uses App Groups, shared containers for apps on the same development team, which can share UserDefaults and files. Both give fine-grained control while blocking other developers' apps.
HTTP conventions that keep an API predictable#
Most mobile-to-backend traffic runs over HTTP: universally supported, stateless (so servers don't track sessions), persistent-connection capable in HTTP/1.1 and HTTP/2, format-agnostic, header-driven, and secured by HTTPS. Every request carries four things — a verb, a resource path, headers (auth tokens, expected formats), and optional data in the body or as query parameters.
Accept: application/json
Authorization: Bearer <token>
GET https://my-backend.com/v1/news?after=timestamp&limit=30
Query parameters are how GET requests filter, sort, and paginate without a body — and they keep the call idempotent, meaning repeated calls don't change server state.
REST conventions worth following#
REST layers standard practice on top of HTTP. The verbs map to CRUD, and consistency is what makes an API easy to consume:
- GET read · POST create · PUT update · DELETE remove
-
Give each resource a unique URI —
GET /users,GET /users/1. -
Use query params for filtering/sorting/paging —
GET /users?role=admin&sort=lastname_asc&page=2&limit=30. -
Name resources with plural nouns —
GET /posts/{postId}, not/query-postsor singular/post. -
Nest related resources —
GET /v1/posts/{postId}/attachments/{attachmentId}. -
Version globally —
GET /v1/posts/{postId}/attachments, not/posts/{postId}/v1/attachments.
Lean on the standard status codes so clients can react correctly:
Choosing an encoding format#
HTTP doesn't care about the payload format — client and server just agree via Content-Type
and Accept. JSON dominates modern REST because it's readable, compact relative to XML, and parses with standard libraries
everywhere.
| Format | Strengths | Costs | Use when |
|---|---|---|---|
| JSON | Human-readable, universal, compact vs. XML | Slower/larger than binary; no built-in schema | Most web and mobile APIs |
| XML | Rich structure, metadata, XSD validation | Verbose, larger, slower to parse | Enterprise, document-centric data |
| Protocol Buffers | Compact binary, fast, strict schema, versioned | Not readable; needs .proto files and codegen | Performance-critical, large-scale transmission |
Lyft moved from ad-hoc REST+JSON to an IDL built on Protocol Buffers, handling both gRPC and legacy HTTPS+JSON. Reddit and Airbnb migrated to GraphQL to serve mobile clients better. Snapchat adopted QUIC to speed up mobile networking.
Prove who you are, cheaply and statelessly#
Any app with personalized or sensitive data needs to verify users before granting access. Token-based authentication is the mobile default: stateless (no server-side session store, so it scales), cross-platform, capable of embedding permissions and expiry, and light on the backend since validation doesn't hit the database each time. The usual vehicle is a JSON Web Token (JWT) — a compact, self-contained token carrying claims as JSON.
- Login. The client sends credentials to the auth service, which validates them.
- Token issue. On success the backend returns an access token (JWT, short-lived — 15–30 min, carrying identity and permissions) and a longer-lived refresh token.
- Secure store. Both go into platform secure storage — Android Keystore-backed encryption before SharedPreferences/DataStore, or iOS Keychain protection classes that keep them encrypted and only readable when the device is unlocked.
-
Authenticated requests.
The app attaches
Authorization: Bearer <access_token>; an API gateway verifies the JWT (often via the identity provider's JWKS public keys) before forwarding.
Renewal:
when the access token expires — detected proactively or on a 401
— the app sends the refresh token to a renewal endpoint, receives a fresh access token (sometimes a new refresh
token too), stores it, and retries the original request. If the refresh token is invalid, log the user out.
Layer on multi-factor and biometric auth, certificate pinning against man-in-the-middle attacks, a trusted-device registry, device fingerprinting, new-device alerts, and suspicious-activity detection as the data's sensitivity warrants. Robinhood marks devices "trusted" after MFA so repeat logins skip the second factor — security balanced with convenience.
An external Identity Provider can issue and validate tokens and expose a JWKS endpoint, letting your systems verify JWTs with public keys without ever handling private signing keys.
Serve heavy, stable content from close by#
A Content Delivery Network spreads servers across regions so static content is served from near the user rather than from the origin backend. In mobile apps it shines for content that's bandwidth-heavy but changes rarely: images and icons, video, HTML, and bundled assets. Every request the CDN serves is one the backend doesn't — that's the whole point.
| Upside | Downside |
|---|---|
| Lower latency and faster loads from nearby edges | Cost grows with usage |
| Absorbs volume spikes without bottlenecks | Configuration, cache management, client integration |
| Redundancy and easy scaling | Your uptime now partly depends on the provider |
| Offloads static content from the origin | Keeping content fresh needs careful cache control |
Never send the whole list#
Any time a design shows a list, ask two questions: is there a natural cap on how many items to show at once, and would sending the full set strain the network or device? If the full set is expensive, paginate — load in batches to protect performance, scalability, and memory. Two approaches dominate.
Offset pagination#
Request a page and size — GET /v1/feed?page=3&limit=30
— and the backend skips (page-1)×limit
rows and returns the next batch, usually with total_pages
/total_items
metadata. It's simple, maps to SQL, and lets users jump to any page. But it degrades as the dataset grows
(counting and skipping rows gets costly) and misbehaves on fast-changing data — items shift between pages,
causing duplicates or gaps. Best for moderate, stable datasets with simple navigation: educational content,
e-commerce catalogs, forums.
Cursor-based pagination#
Think of a bookmark. The client sends a cursor for its current position —
GET /v1/feed?after=<cursor>&limit=30
— and the response returns data plus next_cursor
/prev_cursor. The cursor maps to a stable ordering key (a primary key or timestamp), often Base64-encoded to survive URLs.
It handles frequently changing data well, allows a different page size per call, and keeps the backend
stateless. The catch: no jumping to an arbitrary page — you traverse sequentially. Meta and Slack use it for
their APIs.
Choosing the limit#
The right batch size depends on content weight — light content tolerates higher limits, heavy content needs lower ones:
Adaptive pagination [+] goes further, tuning the limit to network conditions or behavior — a larger limit for fast scrollers, a smaller one for lingering readers.
| Method | Strengths | Limits | Use for |
|---|---|---|---|
| Offset | Simple, jump to any page, maps to SQL | Slows on big data; duplicates on churn | Moderate stable sets: catalogs, forums |
| Cursor | Scales, stable under churn, flexible limit, stateless | No random page access; may need encoding | Real-time large feeds, event logs |
Slack migrated from offset to cursor pagination for consistent retrieval at scale. Discord virtualizes infinite lists, cutting startup memory 14% for heavy users. Shopify uses relative cursors keyed on the last-seen item ID.
Measure first, then optimize#
Performance drives satisfaction, retention, and revenue — Google even downranks slow apps in the Play Store, and crashes, heavy data use, battery drain, and UI jank are top reasons people uninstall. Raise it when discussing data-heavy features, animations, background work, or low-end devices.
Cover the whole stack#
Ground it in data#
Optimize against evidence, not hunches: capture baseline metrics, run continuous monitoring in production across real devices and networks, add targeted instrumentation on critical paths, and validate incrementally after each change. Small, measured improvements beat large speculative refactors. In interviews, be ready to explain how a background component — a prefetcher, a location feature — keeps its resource use in check.
Robinhood used Server-Driven UI to speed feature delivery. Spotify batched metadata downloads with precomputed sorting to cut startup and view-load times. Lyft cut ANRs 21% by optimizing disk reads and adding memory caching.
Getting features to users without breaking them#
Solid architecture is the foundation; deployment strategy is how innovations reach users safely. These operational topics rarely dominate an interview, but raising them signals you think past implementation to the whole product lifecycle.
Gradual release#
Roll new versions out to a slice of users first, so problems surface while they're still small. Google Play offers staged rollouts with fine control — start at 1% and climb — plus the ability to halt and revert affected users. Apple's App Store uses a fixed 7-day phased release and, critically, has no rollback: once a user updates, they stay until the next update. Before a wide release, both platforms offer beta programs (TestFlight, Play beta), internal testing tracks, and employee dogfooding. Pair rollouts with strong monitoring and clear severity thresholds — e.g. pause if crash rate rises above 1%.
Force upgrading#
Sometimes old versions must be retired. Force upgrading prompts outdated clients to update before continuing. It adds friction, so justify it: backend-compatibility breaks, security vulnerabilities, regulatory requirements, or analytics consistency. It exists on a spectrum:
- Hard upgrade — blocks all use until updated; a modal to the app store. For critical security or backend incompatibility.
- Soft upgrade — a dismissible prompt with persistent reminders; less disruptive, slower to migrate users.
- Tiered grace periods [+] — start soft, harden to required after a set window.
Mechanically: on launch the client sends its version to a version-check endpoint, the backend decides whether an upgrade is required and of what kind, and the client shows the matching UI. Mind poor connectivity (offline grace periods), old unsupported devices, and staged enforcement so you don't overwhelm app-store or support channels. Uber built a force-upgrade process to manage scaling.
Feature flags, remote config, and A/B testing#
These separate feature availability from code deployment. On launch, the app asks the backend which features to switch on, based on user traits, device capabilities, environment, or experiment cohort — managed by product teams through a CMS without engineering per change. Because the backend controls which variant a user sees, the same machinery powers A/B tests: track metrics per variant and decide with data. Uses include risk containment, staged rollouts, performance experiments, and traffic-shifting during backend migrations. Costs: branching complexity, technical debt from stale flags, combinatorial testing, and murkier UX understanding — so clean flags up after launch.
Netflix runs A/B tests on nearly every change through its "ABlaze" experimentation platform. Duolingo builds an experimentation framework right into the app and credits much of its product success to it.
Observability, analytics, and monitoring#
A complete view spans technical performance monitoring, user-behavior analytics, error tracking, and business metrics. Classify errors deliberately — fatal (crash), non-fatal (feature breaks, app survives), and degraded (works but slower) — and instrument the last two explicitly, since crash reporters only catch the first. Design for low overhead: sampling, batching and compression, offline buffering so nothing is lost without connectivity, and automatic context (device, app version, user segment) for multidimensional analysis. Analytics usually shows up as a cross-cutting repository in the architecture.
Pinterest built a schemaless end-to-end JSON logging system for its clients. Slack's SlackTrace standardized notification-flow tracing to simplify debugging. Signal collects virtually no analytics — no typical mobile analytics or crash SDKs — to protect privacy.
Localization#
Localization is far more than translation — it's number/currency and date/time formatting, pluralization rules, right-to-left layouts (Arabic, Hebrew, Persian), and culturally aware colors and imagery. iOS has built-in translation export; Android localizes through its resource system. Plan for text expansion (German and Finnish often need 30–40% more space than English; Chinese less), dynamic language switching without a restart, asset-size growth per language, exploding test matrices, and backend responses that honor a language preference. Treat it as architecture, not an afterthought.
Netflix, in 190+ countries, built a pipeline to ingest, translate, and serve UI strings — and uses pseudo-localization (elongated, accented text) as a test step to prove layouts survive other scripts.
Privacy#
Privacy is now a requirement, not a feature. Apps often span jurisdictions with distinct frameworks — the EU's GDPR, the UK's DPA, US HIPAA for health data, Australia's APPs — and the landscape keeps shifting. Protect data across its lifecycle: data minimization, secure transit, protected storage (encrypt sensitive data with AES-256 and manage keys well), access controls, anonymization/pseudonymization, and regular security testing. Track the annually updated OWASP mobile risks list.
Meta has explored Federated Learning with Differential Privacy to improve products without exposing user data.
Accessibility#
Inclusive design widens reach and is often legally required (government, education, healthcare). Follow WCAG, Apple's Human Interface Guidelines, and Material Design accessibility guidance. Support screen readers (VoiceOver, TalkBack), dynamic text sizing, sufficient color contrast, and large touch targets. Test with automated tools (Accessibility Scanner, Accessibility Inspector), manual assistive-tech walkthroughs, and — most valuable — real users with disabilities. The BBC's Mobile Accessibility Guidelines are a strong public reference.
Push notifications#
Push reaches users when the app is closed — via APNs on iOS and FCM on Android, both acting as intermediaries between your backend and devices. The flow: the app registers with the platform service, receives a device token, sends it to the backend; when needed, the backend hands a message to the platform service, which attempts delivery.
Offline queuing that eventually drops, battery-saver delays, platform throttling, payload-size caps, manufacturer background-handling quirks, and user permission or Do-Not-Disturb settings all block delivery. Treat push as a supplementary channel — always have another path for essential updates.
Slack broadened its notification system beyond direct messages and mentions. Duolingo built a high-scale notification system for its engagement campaigns.
App size#
Size affects acquisition and retention — Google's research shows each extra 6MB of APK cuts install conversion by roughly 1%, worse on limited data plans and constrained storage. The two big contributors are third-party libraries (each should justify its footprint) and media assets (use vector graphics, on-demand downloads, server-side image optimization). Name platform tools: R8 code shrinking, resource shrinking, and App Bundles on Android; app thinning, on-demand resources, and whole-module optimization on iOS. Instagram Lite launched at just 573 KB.
CI/CD#
Continuous Integration builds, tests, and statically analyzes on every push — via Fastlane, GitHub Actions, or Bitrise — turning problems into immediate feedback. Continuous Delivery automates signing, environment config, release builds, and distribution to test environments; mobile signing is distinctive (Android keystores; iOS provisioning profiles and certificates, often via Fastlane Match), and each environment needs its own config (Gradle flavors/buildConfigField; Xcode configurations/schemes). True Continuous Deployment is capped by app-store review, so releases aren't instant. The payoff is consistency — the same proven process every time.
Duolingo cut Android and iOS build times 68%. Pinterest's iOS team moved to Bazel and cut clean-build time 21%.
Security#
Security protects user data, system integrity, and access while keeping the experience smooth. Rather than deep implementation, know the domains:
Client-side security is one layer — it only works alongside a well-secured backend. Signal centers its whole design on privacy, with end-to-end encryption that keeps even its own servers from reading user content.
One app, many screens and OS versions#
Fragmentation across hardware, screen sizes, and OS versions shapes design choices. These topics may not lead an interview but often surface in the deep dive, where showing you've considered them signals maturity.
Form factors#
The strategy is strategic modularization: business logic and the data layer stay consistent across devices; the UI and navigation layers adapt. Expectations shift by device — tablets show list and detail side by side; foldables transform between phone and tablet states dynamically; wearables demand glanceable, simplified interfaces; TVs need remote-friendly layouts viewed from a distance. Declarative UI (Jetpack Compose, SwiftUI) makes adaptive layouts far easier. Some data-layer tuning helps too — caching by available memory, per-form-factor image resolutions, prefetching tuned to expected connectivity.
Spotify adapts across screen sizes for a consistent experience. Google Photos added responsive layouts for tablets, foldables, and ChromeOS. Microsoft optimized Outlook, Teams, and Office for large-screen Android with multi-window and multi-instance support, lifting active users and retention.
OS and SDK versions#
Version support trades reach against complexity. The minimum version sets which devices can run the app — a data-driven call weighing OS distribution, required platform capabilities, testing capacity, and the security profile of old versions. The target version (usually the latest stable release) determines which new features you can use; update it regularly for performance, security, and modern APIs. The gap between them dictates how much version-specific code you carry. Manage API deprecation proactively — review warnings, plan migrations, test on old and new. Test strategically: full coverage on min/target/one middle version, smoke tests everywhere, deeper tests on version-specific code.
iOS users upgrade faster; Android users are often held back by manufacturers or device age. Reassess minimum-version requirements as analytics shift, using each major release as a chance to drop a diminishing tail of old versions.
Recap#
Every building block here is a way of drawing a boundary — between layers, between data owners, between availability and deployment, between what's cached and what's authoritative. Mobile system design is mostly the discipline of putting those boundaries in the right places and defending the trade-offs each one makes.
Further reading#
Grouped by topic, each with a one-line reason to read it. Every link was fetched and confirmed live on 24 August 2026. Six source links did not resolve and are omitted — see the note at the end.
Architecture & patterns#
UI-layer architectures#
Architecture in practice#
Testing#
Data storage#
Networking & API design#
Authentication#
Pagination#
Performance#
Releasing & experimenting#
Localization#
Privacy & security#
Accessibility#
Push notifications#
App size & CI/CD#
Supporting different devices#
Six of the source's references failed verification and were left out rather than linked to broken pages:
Uber's plugin-system/RIBs post [17] and Uber's passkeys post [66] (both return 406 to automated fetches —
reachable in a signed-in browser); Instagram Engineering's prefetching [75] and Data Saver [76] posts
(repeated timeouts); Uber's failover-handling post [80] and the CDC HIPAA page [96] (both return 404 —
genuinely moved or removed). Reference [55], a malformed init(suiteName:)
link, is broken in the source itself. Their concepts are all covered in the prose above.