Design the client of a Tinder-style swipe app: card prefetching, instant feel, and swipes that must not be lost or duplicated on a bad network.

26.8.4 · L5 · Interview

Reported at: Uber

Predict first

What does the deck and outbox report?

struct Card: Equatable { let id: Int }
struct Swipe: Equatable { let card: Int, like: Bool, key: String }

struct Deck {
    private(set) var cards: [Card] = []
    private(set) var position = 0
    private(set) var outbox: [Swipe] = []                 // swipes the server has not acknowledged yet
    let lowWater = 3
    var needsMore: Bool { cards.count - position <= lowWater }
    mutating func append(_ more: [Card]) { cards += more }
    mutating func swipe(like: Bool) {
        guard position < cards.count else { return }
        let c = cards[position]; position += 1
        outbox.append(Swipe(card: c.id, like: like, key: "swipe-\(c.id)"))   // key makes a retry harmless
    }
    mutating func acknowledged(_ keys: Set<String>) { outbox.removeAll { keys.contains($0.key) } }
}
var deck = Deck()
deck.append((1...5).map(Card.init))
print("needs more:", deck.needsMore)
deck.swipe(like: true); deck.swipe(like: false)
print("position \(deck.position), needs more: \(deck.needsMore)")
deck.append((6...10).map(Card.init))
print("after refill needs more:", deck.needsMore)
print("outbox:", deck.outbox.map(\.key))
var server = Set<String>()                                 // server dedupes by idempotency key
func send(_ batch: [Swipe]) -> Set<String> { for s in batch { server.insert(s.key) }; return Set(batch.map(\.key)) }
_ = send(deck.outbox)                                       // first attempt: reply lost, client does not know
_ = send(deck.outbox)                                       // retry after reconnect
print("server stored:", server.count)
deck.acknowledged(send(deck.outbox))
print("outbox after ack:", deck.outbox.count)
  • needs more: false / position 2, needs more: true / after refill needs more: false / outbox: ["swipe-1", "swipe-2"] / server stored: 2 / outbox after ack: 0
  • needs more: true at the start
  • server stored: 4 after the retry

Mental model. A dealer who keeps a few cards fanned out ahead of you, and a post box that keeps every card you play until the receiver stamps it received, so a retry never means two plays.

Reported at Uber (anonymous, unverified). The deck is a local queue with a low-water mark: keep the next several cards (and their images, decoded and downsampled, Q327) ready so a swipe never waits; when the remaining count drops to the mark (3 here), fetch the next page in the background, using a cursor from the server. The program shows needsMore flipping to true after two swipes from five cards, and back to false after a refill. Keep memory bounded by dropping cards and images that are well behind the current position. The swipe itself must be instant: update the UI at once, do not wait for the server.

That makes each swipe an offline-first write: put it in a persistent outbox with an idempotency key (here derived from the card id), send in the background, and remove it only when the server acknowledges. If the reply is lost and the client retries, the server sees the same key and stores it once: the check sends the outbox twice and the server still holds 2 swipes; after an acknowledgement the outbox is empty. Persist the outbox (not just memory) so a crash or a force quit does not drop swipes, and flush on reconnect and on foreground. Matching is a server concern, so the client only has to show a match when the server says so (a push or the response), and handle “undo” (a server operation, rate limited or premium). Other points: rate limiting of free swipes, accessibility alternatives to the gesture, and haptics. Only the deck and outbox logic is run here, not the server or the UI.

A low-water mark triggers prefetch, swipes wait in an outbox with idempotency keys, and a repeated send is stored once.

Common traps

  • Waiting for the server before showing the next card. It makes the app feel slow; update locally.
  • Keeping unsent swipes only in memory. A crash loses them.

They might follow up with

  • How do you handle undo? Send an undo operation for the last swipe key; the server applies it only if the other side has not already matched.
  • How do you keep images smooth? Prefetch with UIImage or an image pipeline ahead of time and downsample to the card size.

Related questions

  • 12.7.1 How do you design an image loading pipeline: decoding, downsampling, caching and de-duplication?
  • 26.1.3 Design an offline-first reader app: content model, sync, storage budget, prefetching, reading position and…
  • 26.8.3 Design the client of a flight booking app: search, seat hold, payment and confirmation. How do you handle a…

← Design the client of a flight booking app: search, seat hold, payment…   Design an image browsing app (a grid of photos you can open full… →