How do you set payload budgets and reduce bytes: field selection, deltas, compression, image sizes, and what fan-out on write versus read means for the client?

26.2.2 · L5 · Interview

Predict first

A list of 200 repetitive JSON items is compressed, and a 5-item delta is compared with the whole list. What do the checks report?

import Foundation

let rows = (0..<200).map {
    "{\"id\":\($0),\"title\":\"Article number \($0)\",\"author\":\"Ana Lopez\",\"tags\":[\"swift\",\"ios\"],\"published\":\"2026-11-03T10:00:00Z\"}"
}
let full = Data(("[" + rows.joined(separator: ",") + "]").utf8)
let changed = Data(("[" + rows.prefix(5).joined(separator: ",") + "]").utf8)
func compressed(_ data: Data) -> Int { (try? (data as NSData).compressed(using: .zlib).length) ?? -1 }

print("compression shrinks the full list more than 5x:", Double(full.count) / Double(compressed(full)) > 5)
print("a 5-item delta is under a tenth of the full list:", changed.count < full.count / 10)
  • compression shrinks it more than 5x: true; the delta is under a tenth: true
  • compression makes it larger
  • the delta is as big as the list

Mental model. Sending a whole newspaper every morning to people who only want the sports page is wasteful. Send the page they asked for, only what changed since yesterday, and fold it small.

Bytes are latency, battery and (for some users) money, so give every important screen a payload budget (a target for the data needed before the screen is usable, agreed with the backend team and enforced by a test or a dashboard) and measure it with the network instruments and URLSessionTaskMetrics (Q456). The main tools. Ask for less: field selection (GraphQL or sparse fieldsets, Q325), pagination with a sensible page size (Q318), and summaries for lists with the detail fetched on demand. Send changes, not copies: incremental sync by cursor or updated_since, conditional requests with ETag so unchanged data costs a header (Q319), and delta payloads, as the check below shows a five-item delta is a small fraction of the full list. Compress: JSON and text compress very well (the same list shrinks more than fivefold), and URLSession negotiates standard HTTP compression for you, so make sure the server enables it; for images use modern formats and the right pixel size for the screen scale (Q327), never full-resolution originals for a thumbnail. Remember small payload ceilings: a push notification carries only a few kilobytes (Q375), so it carries an id and the app or a notification service extension fetches the rest.

Fan-out is how an event reaches many readers: fan-out on write pushes a new post into each follower’s precomputed timeline at write time (fast reads, expensive writes for accounts with millions of followers), while fan-out on read assembles the timeline when asked (cheap writes, slower reads), and real systems mix them. The client consequences are what an iOS engineer should say: a timeline is a cursor-paginated, partly precomputed list that can have gaps and reordering; new items arrive by push or poll and are merged into the local database without jumping the scroll position (“N new posts” pill); deletions and edits need tombstones and versions; and a celebrity-scale event can cause a thundering herd on refresh, so jitter background refresh and use ETag/304 to keep it cheap. Include cost awareness: respect Low Data Mode (allowsConstrainedNetworkAccess, Q437), use waitsForConnectivity, and avoid autoplaying video on cellular without consent.

Repetitive text compresses very well, and sending only what changed cuts the size by an order of magnitude.

Common traps

  • Fetching full objects for list rows. A list needs a summary; fetch details on demand.
  • Refreshing everything on a fixed timer for every user. It creates synchronised spikes; use conditional requests and jitter.

They might follow up with

  • What is a payload budget? A numeric target for the data a screen needs, tracked and enforced like a performance budget.
  • Fan-out on write or read for a social feed? Usually a mix: precompute for normal accounts and merge at read time for very large ones.

Related questions

  • 26.2.1 How do you design a client for reliability: deadlines, circuit breakers, degradation tiers, bulkheads, kill…
  • 26.2.3 How do you design analytics and experimentation that respect privacy: event schemas, consent, minimisation…
  • 26.1.3 Design an offline-first reader app: content model, sync, storage budget, prefetching, reading position and…
  • 12.3.1 How do you decode and paginate an API response, and which pagination style is safest?
  • 12.3.2 How does HTTP caching work with Cache-Control, ETag and conditional requests, and how does URLCache fit in?
  • 12.6.1 REST, GraphQL or gRPC: what are the client-side trade-offs?
  • 12.7.1 How do you design an image loading pipeline: decoding, downsampling, caching and de-duplication?
  • 17.1.2 How does the Apple Push Notification service deliver a push: tokens, priority, collapse, expiry, and what can…
  • 23.2.1 How do you debug layout and views with the view debugger, and how do you inspect what your app really sends…
  • 21.6.1 Where does an app’s energy go, and how do timers, networking, location and animation change the battery bill?

← How do you design a client for reliability: deadlines, circuit…   How do you design analytics and experimentation that respect privacy… →