Irbisa · Cheatsheet

iOS System Design

The design round: feed, chat, live tracking, uploads, pagination, caching, idempotency, server-driven UI, analytics, super-apps

Senior Developer · 16 questions

  1. 01

    You have 45 to 60 minutes to design an iOS feature on a whiteboard. How do you split the time, and what is the interviewer actually grading?

    Spend the first ten minutes pinning down requirements, then sketch the client architecture and the API, then go deep on the one or two parts that are hard on a phone.

  2. 02

    REST, GraphQL or gRPC: which API style do you propose for the iOS client, and what does each one cost the app?

    Default to REST with JSON: URLSession, Codable, HTTP caching and every debugging proxy work out of the box.

  3. 03

    Why does offset pagination show duplicates and gaps in a feed that changes while the user scrolls, and what does a cursor fix?

    An offset counts rows from the top, so every post added or deleted above the user's position shifts the window: an insert repeats the last item of the previous page, a delete skips one.

    -- Offset: "skip 40 rows". One post inserted at the top shifts every row down,-- so page 3 starts with the last post of page 2. A deleted post is skipped the same way.SELECT id, created_at FROM postsORDER BY created_at DESC, id DESCLIMIT 20 OFFSET 40;…
  4. 04

    Every screen must be "fresh", so the app sends forty requests on cold start. Which data do you cache, for how long, and where is stale data acceptable?

    Ask what a stale value costs for each kind of data, not whether it may be stale.

  5. 05

    A banking home screen shows six widgets from six services and stays blank until the slowest answers. What do you change on the client and the backend?

    Let every widget load, fail and retry on its own, starting from its last cached value, so the screen is never hostage to its slowest dependency.

    enum Section: CaseIterable, Sendable { case accounts, cards, loans, savings, cashback, offers }enum SectionState: Sendable { case loading, loaded(SectionData), failed } @MainActor @Observablefinal class HomeModel {  private(set) var sections: [Section: SectionState] = [:]   // filled from the disk cache first…
  6. 06

    Find the bug: tap Like and then Unlike quickly on a weak network, and the heart stays filled while the server has the post unliked.

    @MainActor final class PostCellModel {  private(set) var post: Post  private let api: any LikeAPI  init(post: Post, api: any LikeAPI) { self.post = post; self.api = api }   func likeTapped() {    post.isLiked.toggle()                       // optimistic: the heart fills at once    post.likeCount += post.isLiked ? 1 : -1    Task {      do { try await api.toggleLike(postID: post.id) }      catch {        post.isLiked.toggle()                   // roll back        post.likeCount += post.isLiked ? 1 : -1      }    }  }}

    Two relative toggleLike requests race, and the rollback flips the current state instead of restoring the server's: when the first request times out after the server applied it, its catch re-fills the heart that the second tap had just emptied.

  7. 07

    A payment request times out and the user taps Pay again. How do the client and the API make sure the card is charged only once?

    With an idempotency key that names the payment, not the request: the app creates it once per order, saves it before sending and reuses it on every retry, and the server stores the first result under that key and returns it for any repeat instead of charging again.

    // The key names the payment, not the HTTP requeststruct PaymentIntent: Codable, Sendable {  let key: UUID  let orderID: String  let amount: Decimal}…
  8. 08

    A flash sale should end at midnight, yet some users can still open it the next morning. Which clock does the app trust, and who enforces the end?

    No clock on the device: the user can change the date in Settings, and "midnight" differs by time zone.

    // The API sends absolute instants: { "serverTime": "...Z", "offers": [{ "endsAt": "...Z" }] }struct Offer: Decodable, Sendable {  let id: String  let endsAt: Date                           // an instant in UTC, never "until midnight"}…
  9. 09

    Design the feed of an Instagram-like app: data flow, pagination, prefetching, media, and getting the user back to the same post after a relaunch.

    The feed is a local cache the UI observes: a repository pages from the API by cursor into a store, the list renders from the store, media loads ahead of the scroll at the size of the cell, and the position is saved as an anchor post id so a relaunch can open where the user left off.

  10. 10

    Design a stories viewer like Instagram's: what do you preload, how many video players do you keep alive, and what is the memory budget?

    Preload one step ahead in each direction the user can go: the next segment of the current story and the first segment of the next person's.

  11. 11

    Design posting a marketplace listing with ten photos: compression, an upload queue, retries, and a draft that survives the app being killed.

    Save the draft and processed photos locally first, upload each photo as a file task in a background URLSession, and create the listing with an idempotent request once every photo has a server id.

  12. 12

    Design a messenger client like Telegram: transport, message states, local storage, ordering, and what happens offline or in the background.

    The local database is the chat: the UI renders from it, an outgoing message is written there first with a client id, and a sync layer uses a socket while the app is open and catches up by sequence number after every gap.

  13. 13

    Design live courier tracking: how does the courier's location reach the customer's screen, and what changes once the app is in the background or the phone is locked?

    The courier's app sends throttled location fixes to the server, which matches them to the road and pushes them to the customer over a socket while the map is open.

  14. 14

    How would you build the app's own analytics layer: batching events, keeping them offline, sampling, honouring consent and switching vendors without touching features?

    Features call one typed track API that returns at once; events land in a disk-backed queue, leave in compressed batches with a unique id each, and fan out to vendor adapters behind a protocol.

  15. 15

    When is server-driven UI worth it, and how does an old app version cope with a component it has never seen?

    It pays off on screens product rearranges weekly (home, promos, catalogue layouts), where App Review and slow updates are the bottleneck.

    // { "type": "stories", "props": { ... },//   "fallback": { "type": "banner", "props": { "title": "New: stories", "url": "app://stories" } } }indirect enum Component: Decodable, Sendable {  case banner(Banner)  case productGrid(ProductGrid)  case unsupported(fallback: Component?)      // a type this build has never seen…
  16. 16

    A dozen teams ship banking, taxi and food into one iOS super-app. How do you structure it so no team slows launch or another team's release?

    As a thin host app on a shared platform layer, with a pair of modules per business: a small interface module other teams may import and an implementation only the host links.