iOS System Design
Designing iOS modules and libraries — concurrency, offline support, networking, and extensibility.
16 questions
JuniorTheoryVery commonHow do serial and concurrent GCD queues differ, and where must UI work run?
How do serial and concurrent GCD queues differ, and where must UI work run?
GCD schedules work on dispatch queues. A serial queue runs tasks one at a time and can guard shared state without locks; a concurrent queue runs many at once. All UI updates must run on the main (serial) queue.
Common mistakes
- ✗Believing a serial queue runs tasks concurrently
- ✗Updating UI from a background queue instead of the main queue
- ✗Thinking GCD makes shared state thread-safe automatically
Follow-up questions
- →Why can a serial queue replace a lock for protecting shared state?
- →What happens if you call a UI update from a background queue?
SeniorDesignVery commonDesign a reusable image-loading library used across many screens and lists in an iOS app. Given a URL it returns a decoded image for a cell. Requirements: a two-tier cache — a fast in-memory cache plus a disk cache that survives relaunch; decode and downsample images off the main thread so scrolling stays smooth; cancel an in-flight request when its cell is reused so a recycled cell never flashes the previous image; coalesce duplicate requests for the same URL so the network is hit once; and expose a small, stable public API (one call to load into a target, one to cancel) that hides all of this. How do you structure the cache tiers, the decode pipeline, cancellation on cell reuse, deduplication, and the public surface?
Design a reusable image-loading library used across many screens and lists in an iOS app. Given a URL it returns a decoded image for a cell. Requirements: a two-tier cache — a fast in-memory cache plus a disk cache that survives relaunch; decode and downsample images off the main thread so scrolling stays smooth; cancel an in-flight request when its cell is reused so a recycled cell never flashes the previous image; coalesce duplicate requests for the same URL so the network is hit once; and expose a small, stable public API (one call to load into a target, one to cancel) that hides all of this. How do you structure the cache tiers, the decode pipeline, cancellation on cell reuse, deduplication, and the public surface?
Layer a fast in-memory cache over a disk cache that survives relaunch, checking memory, then disk, then network. Decode and downsample on a background queue so the main thread only draws. Coalesce requests keyed by URL so the network is hit once, and return a cancellable token so cell reuse cancels the stale load. Keep the public surface tiny — one load-into-target call and one cancel.
Common mistakes
- ✗Decoding images on the main thread instead of a background queue
- ✗Caching only in memory, so every relaunch re-downloads everything
- ✗Not cancelling on cell reuse, so a recycled cell shows a stale image
Follow-up questions
- →How do you size and evict the in-memory tier under memory pressure?
- →How does the cancellable token reach the right in-flight task on reuse?
JuniorTheoryCommonHow does URLSession fetch data, and how do you add offline cache support?
How does URLSession fetch data, and how do you add offline cache support?
URLSession runs HTTP requests via data tasks, returning data, response, and error. For offline support you persist successful responses locally (URLCache, a disk store, or a database) and serve the cached copy when a request fails, showing last-known data offline.
Common mistakes
- ✗Assuming
URLSessionblocks the caller rather than running tasks asynchronously - ✗Expecting offline support to be automatic without persisting responses
- ✗Caching in memory only, losing data when the app is killed
Follow-up questions
- →When would you store responses in a database rather than URLCache?
- →How do you decide between serving cache and showing an error on failure?
MiddleDesignCommonDesign a 'download for offline' feature for an iOS podcast app so users can save episodes to play without a connection. Requirements: a download queue the user can add to, reorder, and pause or resume; a per-app storage budget the user sets (for example 4 GB), enforced so saved downloads never exceed it; an eviction policy that removes the least useful saved episodes when the budget is reached — for instance, the oldest already-played first; and clear feedback when the disk is full or the budget is hit, telling the user what will be removed or that they must free space, rather than failing silently. How do you structure the download queue, the storage-budget accounting, the eviction policy, and the disk-full user experience?
Design a 'download for offline' feature for an iOS podcast app so users can save episodes to play without a connection. Requirements: a download queue the user can add to, reorder, and pause or resume; a per-app storage budget the user sets (for example 4 GB), enforced so saved downloads never exceed it; an eviction policy that removes the least useful saved episodes when the budget is reached — for instance, the oldest already-played first; and clear feedback when the disk is full or the budget is hit, telling the user what will be removed or that they must free space, rather than failing silently. How do you structure the download queue, the storage-budget accounting, the eviction policy, and the disk-full user experience?
Back it with a persistent, reorderable download queue that supports pause and resume. Track total saved bytes against the user's budget and evict before exceeding it, by a least-useful rule — the oldest already-played episodes first. On a real disk-full or a budget hit, tell the user exactly what will be removed or that they must free space, instead of failing silently.
Common mistakes
- ✗Enforcing no storage budget, so downloads fill the whole device
- ✗Evicting the newest or largest episodes instead of the least-useful ones
- ✗Failing silently on a full disk instead of telling the user what happens
Follow-up questions
- →What signals make a saved episode "least useful" to evict first?
- →How does the budget accounting stay correct across a mid-download kill?
MiddleDesignCommonDesign a paginated feed screen for an iOS app — an endlessly scrolling list of posts. Requirements: fetch pages using cursor-based pagination (the server returns an opaque next cursor), not page numbers, so items inserted between requests never shift or repeat; prefetch the next page before the user reaches the bottom so scrolling never stalls; on pull-to-refresh, merge fresh items at the top and drop any that already appear below; and on a cold start with no network, show the last cached page plus a clear offline indicator instead of an empty screen. How do you structure the paging state, the prefetch trigger, refresh-time deduplication, and the offline cold-start path so the list stays smooth and consistent?
Design a paginated feed screen for an iOS app — an endlessly scrolling list of posts. Requirements: fetch pages using cursor-based pagination (the server returns an opaque next cursor), not page numbers, so items inserted between requests never shift or repeat; prefetch the next page before the user reaches the bottom so scrolling never stalls; on pull-to-refresh, merge fresh items at the top and drop any that already appear below; and on a cold start with no network, show the last cached page plus a clear offline indicator instead of an empty screen. How do you structure the paging state, the prefetch trigger, refresh-time deduplication, and the offline cold-start path so the list stays smooth and consistent?
Page with an opaque server cursor rather than page numbers, so inserts never shift or repeat rows. Prefetch the next page when the user nears the end, driven by the list's prefetch hook, so scrolling never stalls. On refresh, merge new items and dedupe by stable id against what is already shown. Persist the last page so a cold offline start renders cached content with an offline indicator, not a blank screen.
Common mistakes
- ✗Using page-number offsets, so inserts between requests shift rows and cause repeats
- ✗Prefetching only at the exact bottom, so scrolling stalls waiting on the network
- ✗Showing a blank screen on offline cold start instead of the last cached page
Follow-up questions
- →Why does an opaque cursor survive inserts that a page number cannot?
- →How do you keep the prefetch from firing several overlapping page requests?
SeniorDesignCommonDesign a chat client for an iOS messaging app. Requirements: messages must display in a stable order even when they arrive out of sequence from the socket; show per-message delivery and read receipts; queue messages typed while offline and send them, in order, once connectivity returns; reconnect the socket automatically after a drop without losing or duplicating messages; and let the user scroll up to page back through older history on demand. The backend exposes a websocket for live messages and a REST endpoint for history pages. How do you structure the local message store and its ordering, the offline send queue, receipt tracking, socket reconnection, and history paging so the conversation stays consistent across drops and relaunches?
Design a chat client for an iOS messaging app. Requirements: messages must display in a stable order even when they arrive out of sequence from the socket; show per-message delivery and read receipts; queue messages typed while offline and send them, in order, once connectivity returns; reconnect the socket automatically after a drop without losing or duplicating messages; and let the user scroll up to page back through older history on demand. The backend exposes a websocket for live messages and a REST endpoint for history pages. How do you structure the local message store and its ordering, the offline send queue, receipt tracking, socket reconnection, and history paging so the conversation stays consistent across drops and relaunches?
Keep a local message store ordered by a server sequence or timestamp, not arrival order, so out-of-order socket frames still render correctly. Persist unsent messages in an offline queue and flush them in order on reconnect, keyed so retries never duplicate. Track delivery and read state per message. Reconnect the socket with backoff and replay any gap. Page older history from REST on demand.
Common mistakes
- ✗Ordering messages by socket arrival instead of a server sequence or timestamp
- ✗Holding the offline send queue in memory, so a relaunch loses unsent messages
- ✗Reconnecting by refetching the whole conversation instead of replaying the gap
Follow-up questions
- →How does the send queue guarantee at-most-once delivery across a reconnect?
- →What key lets the client dedupe a message it both sent and received back?
SeniorDesignCommonDesign a reusable file-downloader library for an iOS app that fetches large files (videos, archives) across many screens. Requirements: run several downloads concurrently but cap the number in flight; resume a partially finished download after the app is killed and relaunched, rather than restarting it from zero; report byte-level progress per download; let callers assign priorities so a user-visible download preempts a background one; and refuse to start when free disk space is too low, failing cleanly instead of filling the device. The library must expose a small, stable API — enqueue, cancel, observe progress — that hides all of this. How do you structure the download queue, background resume, progress reporting, prioritization, and the disk-space guard?
Design a reusable file-downloader library for an iOS app that fetches large files (videos, archives) across many screens. Requirements: run several downloads concurrently but cap the number in flight; resume a partially finished download after the app is killed and relaunched, rather than restarting it from zero; report byte-level progress per download; let callers assign priorities so a user-visible download preempts a background one; and refuse to start when free disk space is too low, failing cleanly instead of filling the device. The library must expose a small, stable API — enqueue, cancel, observe progress — that hides all of this. How do you structure the download queue, background resume, progress reporting, prioritization, and the disk-space guard?
Run downloads on a background URLSession so they survive an app kill and resume from partial data via resume data or an HTTP range request, not from zero. A bounded queue caps concurrency and orders tasks by caller priority so a visible download preempts a background one. Report progress from the session's byte-level callbacks, and check free disk space before enqueuing so a low-space start fails cleanly.
Common mistakes
- ✗Using a foreground session, so a killed app restarts downloads from zero
- ✗Holding partial bytes in memory instead of resuming from saved data on relaunch
- ✗Starting all downloads at once with no concurrency cap or priority ordering
Follow-up questions
- →What does resume data hold that lets a download continue after a relaunch?
- →How does the queue preempt a running background download for a visible one?
MiddleDesignOccasionalDesign a live location-sharing feature for an iOS app, like sharing your position with a friend for a set period. Requirements: sample the device's location at a rate that feels live but does not drain the battery — adapt the rate to movement, sampling frequently while moving and sparsely while still; upload positions in batches rather than one network call per fix, to save radio and battery; keep sharing working while the app is backgrounded; and buffer fixes locally while offline so nothing is lost, uploading them when the connection returns. How do you structure location sampling, the adaptive rate, batched uploads, background operation, and the offline buffer so the feature is both live and battery-friendly?
Design a live location-sharing feature for an iOS app, like sharing your position with a friend for a set period. Requirements: sample the device's location at a rate that feels live but does not drain the battery — adapt the rate to movement, sampling frequently while moving and sparsely while still; upload positions in batches rather than one network call per fix, to save radio and battery; keep sharing working while the app is backgrounded; and buffer fixes locally while offline so nothing is lost, uploading them when the connection returns. How do you structure location sampling, the adaptive rate, batched uploads, background operation, and the offline buffer so the feature is both live and battery-friendly?
Use CoreLocation with an accuracy and distance filter tuned to the use case, and adapt the sample rate to movement — frequent while moving, sparse while still — so the battery is not drained. Buffer fixes locally and upload them in batches instead of one call per fix. Keep sharing alive in the background with the location capability, and when offline, persist fixes and flush them when the connection returns.
Common mistakes
- ✗Sampling at max accuracy regardless of movement, draining the battery
- ✗Sending one network call per fix instead of batching the uploads
- ✗Buffering fixes in memory only, losing them on an offline background kill
Follow-up questions
- →How does an adaptive distance filter cut fixes while the user stands still?
- →How large should a batch grow before it must flush on time, not on count?
MiddleDesignOccasionalDesign offline search over a large local catalog in an iOS app — tens of thousands of products already cached on the device. Requirements: a user must be able to search by name and keywords instantly while fully offline; results should rank reasonably and support prefix and typo-tolerant matching; the index must not bloat memory or launch time, so you cannot hold everything in RAM or rebuild it on every launch; and the catalog updates periodically, so the index has to stay fresh as records are added, changed, or removed without a full rebuild each time. Where does the index live, what structure backs it, and how is it kept in sync with the catalog? How do you structure index construction, on-device storage, query evaluation, and incremental freshness?
Design offline search over a large local catalog in an iOS app — tens of thousands of products already cached on the device. Requirements: a user must be able to search by name and keywords instantly while fully offline; results should rank reasonably and support prefix and typo-tolerant matching; the index must not bloat memory or launch time, so you cannot hold everything in RAM or rebuild it on every launch; and the catalog updates periodically, so the index has to stay fresh as records are added, changed, or removed without a full rebuild each time. Where does the index live, what structure backs it, and how is it kept in sync with the catalog? How do you structure index construction, on-device storage, query evaluation, and incremental freshness?
Build an inverted index — tokens mapped to record ids — and persist it on disk (SQLite FTS or a file store), not in RAM, so it survives launches without rebuilding. Query it directly for prefix and fuzzy matches, ranking by term frequency. Keep it fresh incrementally: on a catalog delta, update only the changed records' postings instead of rebuilding the whole index. Load it lazily so launch stays fast.
Common mistakes
- ✗Holding the whole catalog and index in memory instead of persisting them on disk
- ✗Rebuilding the entire index on launch or on every catalog change
- ✗Requiring the network for search instead of querying a local index
Follow-up questions
- →Why does SQLite FTS suit on-device search better than a linear scan?
- →How does an incremental update touch only changed postings, not the whole index?
MiddleDesignOccasionalDesign a push-driven inbox for an iOS app — a list of messages that arrive by push notification. Requirements: a push may carry the message payload, but the app also fetches from the server on open, so the same message must not appear twice — dedupe the push copy against the fetched copy by a stable message id; the unread badge count must reflect real unread state, staying correct whether the count came from the push payload or a server sync, and clearing as the user reads; and handle the case where the app was offline for a long time and 200 pushes were dropped or coalesced by the system — reconcile by fetching authoritative state from the server rather than trusting that every push was delivered. How do you structure dedup between push and fetch, badge-count truth, and offline reconciliation?
Design a push-driven inbox for an iOS app — a list of messages that arrive by push notification. Requirements: a push may carry the message payload, but the app also fetches from the server on open, so the same message must not appear twice — dedupe the push copy against the fetched copy by a stable message id; the unread badge count must reflect real unread state, staying correct whether the count came from the push payload or a server sync, and clearing as the user reads; and handle the case where the app was offline for a long time and 200 pushes were dropped or coalesced by the system — reconcile by fetching authoritative state from the server rather than trusting that every push was delivered. How do you structure dedup between push and fetch, badge-count truth, and offline reconciliation?
Treat a push as a hint, not the truth — dedupe its payload against the server fetch by a stable message id so a message never appears twice. Derive the badge count from the reconciled unread set rather than by counting pushes, and clear it as items are read. On reopening after many dropped or coalesced pushes, fetch authoritative server state instead of trusting that every push was delivered.
Common mistakes
- ✗Trusting the push payload as truth instead of deduping against a server fetch
- ✗Incrementing the badge per push rather than deriving it from unread state
- ✗Assuming every push was delivered instead of reconciling after a long offline
Follow-up questions
- →What stable id lets the client match a push copy to the fetched message?
- →Why can the system coalesce or drop pushes, forcing a server reconcile?
MiddleDesignOccasionalDesign an iOS module that shows monthly spending/listing statistics. There is no backend contract yet — propose one. The screen must be highly extensible: the number of tabs and the set of data per tab are driven by the backend. All data belongs to a month selected on a chart; switching the month on one tab switches it on all tabs. Pull-to-refresh reloads every tab. Charts may not exist for every tab (the periods match when they do). An existing chart component reports the selected tab. On error with no cache, show a placeholder plus a reload action; on error with cache, show an error message over the cached data. A non-functional requirement is to minimize network traffic over an unstable connection. How do you structure the module, its models, the cross-tab month-sync interface, and the data path from backend to cell?
Design an iOS module that shows monthly spending/listing statistics. There is no backend contract yet — propose one. The screen must be highly extensible: the number of tabs and the set of data per tab are driven by the backend. All data belongs to a month selected on a chart; switching the month on one tab switches it on all tabs. Pull-to-refresh reloads every tab. Charts may not exist for every tab (the periods match when they do). An existing chart component reports the selected tab. On error with no cache, show a placeholder plus a reload action; on error with cache, show an error message over the cached data. A non-functional requirement is to minimize network traffic over an unstable connection. How do you structure the module, its models, the cross-tab month-sync interface, and the data path from backend to cell?
Split into a container that owns the tabs plus a per-tab collection module driven by a cell factory keyed on the backend-described data type, so new tab/cell kinds need no UI changes. Model the selected month as shared state (a coordinator or shared store) so changing it on one tab propagates to all; pull-to-refresh reloads every tab. Cache responses per month/tab to cut traffic, and show placeholder-plus-reload with no cache or an error over cached data when present.
Common mistakes
- ✗Hard-coding tabs and cell types instead of a backend-driven cell factory
- ✗Keeping the selected month per-tab so switching one tab does not sync others
- ✗Reloading only the visible tab on pull-to-refresh instead of all tabs
Follow-up questions
- →What backend response shape lets the client add new cell types with no release?
- →How does the cache distinguish "no data yet" from "load failed"?
SeniorDesignOccasionalDesign an iOS analytics library that can be embedded in any module or app. It sends user events to an in-house backend that accepts aggregated batches. Each event has mandatory common fields (event id, timestamp) plus optional string/number/bool fields. Requirements: never lose events on a crash or app eviction; do not inflate traffic or drain the battery (batch by time and count); allow safe logging from any thread. Complications: several analytics destinations may be active at once and the list can change at runtime; events carry a priority — high is sent immediately, normal is batched, low is sent only on WiFi while charging. How do you structure the library — persistence, batching, thread-safe ingestion, pluggable transports, priority routing — keeping the public API stable as the internals evolve?
Design an iOS analytics library that can be embedded in any module or app. It sends user events to an in-house backend that accepts aggregated batches. Each event has mandatory common fields (event id, timestamp) plus optional string/number/bool fields. Requirements: never lose events on a crash or app eviction; do not inflate traffic or drain the battery (batch by time and count); allow safe logging from any thread. Complications: several analytics destinations may be active at once and the list can change at runtime; events carry a priority — high is sent immediately, normal is batched, low is sent only on WiFi while charging. How do you structure the library — persistence, batching, thread-safe ingestion, pluggable transports, priority routing — keeping the public API stable as the internals evolve?
Buffer events through a thread-safe serial queue, persist them to a disk-backed queue immediately so a crash loses nothing, and flush in batches triggered by time or count. Define a transport protocol so multiple pluggable backends can be added/removed at runtime, and route by priority — high flushes now, normal batches, low waits for WiFi + charging. Keep one small public log API stable while internals evolve.
Common mistakes
- ✗Keeping events only in memory, so a crash before flush loses them
- ✗Ingesting from any thread without synchronization, causing data races
- ✗Hard-coding one backend instead of a pluggable transport protocol
Follow-up questions
- →How do you guarantee at-least-once delivery without sending duplicates?
- →How would low-priority routing detect WiFi-plus-charging without polling constantly?
SeniorDesignOccasionalDesign a client-side feature-flag and A/B-experiment SDK for an iOS app. Requirements: fetch the flag set from a config service and cache it on disk; evaluate flags synchronously and offline, so a screen never waits on the network to decide what to show; keep a given user's assigned experiment variant stable across launches and flag refreshes, so their UI does not flip mid-session; and never block or delay app launch on the fetch — the app must start instantly using the last cached flags, or safe compiled-in defaults on first run. The SDK also reports an exposure event when a flag is read, for later analysis. How do you structure flag storage, offline evaluation, stable variant assignment, the non-blocking refresh, and first-run defaults?
Design a client-side feature-flag and A/B-experiment SDK for an iOS app. Requirements: fetch the flag set from a config service and cache it on disk; evaluate flags synchronously and offline, so a screen never waits on the network to decide what to show; keep a given user's assigned experiment variant stable across launches and flag refreshes, so their UI does not flip mid-session; and never block or delay app launch on the fetch — the app must start instantly using the last cached flags, or safe compiled-in defaults on first run. The SDK also reports an exposure event when a flag is read, for later analysis. How do you structure flag storage, offline evaluation, stable variant assignment, the non-blocking refresh, and first-run defaults?
Cache the flag set on disk and evaluate synchronously from that snapshot, so a screen never waits on the network. Refresh in the background and swap the snapshot atomically, never blocking launch — first run falls back to compiled-in safe defaults. Assign each user a variant from a stable hash of user id and experiment key so it survives refreshes. Log an exposure event when a flag is read.
Common mistakes
- ✗Fetching flags on each read, so a screen blocks on the network to render
- ✗Reassigning a user's variant on refresh, so their UI flips mid-experiment
- ✗Blocking app launch on the flag fetch instead of using the cached values
Follow-up questions
- →Why does a stable hash keep a variant fixed without storing every assignment?
- →How do safe defaults keep a first-run launch correct with no cached flags yet?
SeniorDesignOccasionalDesign the sync engine for a notes app edited on several devices that share one account. Requirements: each device works fully offline and syncs when it comes online; every edit is captured as an entry in a per-device change log rather than overwriting the whole note, so concurrent edits on two devices can be merged; when two devices change the same note before syncing, resolve the conflict predictably — merge non-overlapping changes automatically and, when edits truly collide, keep both versions rather than silently dropping one; and the user must see a clear, non-destructive outcome when a collision happens, for example a conflict copy, never losing their words. How do you structure the change log, the sync protocol, conflict detection and resolution, and the collision user experience?
Design the sync engine for a notes app edited on several devices that share one account. Requirements: each device works fully offline and syncs when it comes online; every edit is captured as an entry in a per-device change log rather than overwriting the whole note, so concurrent edits on two devices can be merged; when two devices change the same note before syncing, resolve the conflict predictably — merge non-overlapping changes automatically and, when edits truly collide, keep both versions rather than silently dropping one; and the user must see a clear, non-destructive outcome when a collision happens, for example a conflict copy, never losing their words. How do you structure the change log, the sync protocol, conflict detection and resolution, and the collision user experience?
Each device appends edits to a local change log and syncs the log, not the whole note, so edits merge instead of overwriting. Track a version per note — per-device counters — to detect concurrency. Merge non-overlapping changes automatically; when edits truly collide, keep both via a conflict copy rather than dropping one. The user sees a non-destructive outcome and never loses text.
Common mistakes
- ✗Syncing whole-note overwrites with last-writer-wins, losing one side's edits
- ✗Detecting conflicts by timestamp alone instead of per-device versioning
- ✗Silently dropping a colliding edit instead of keeping both non-destructively
Follow-up questions
- →How does per-device versioning tell a true conflict from a fast-forward?
- →When is a conflict copy better than an automatic three-way merge?
SeniorDesignOccasionalDesign a video playback screen for an iOS app that streams long-form video. Requirements: use adaptive bitrate over the streaming protocol HLS so quality tracks the available bandwidth without the user switching manually; prebuffer enough ahead that playback starts fast and rarely stalls; apply a caching policy so replays and short seeks do not refetch from the network; and handle the network dropping mid-play gracefully — keep playing to the buffered edge, show a buffering state, and resume from the same position when the connection returns, without restarting the video from the beginning. How do you structure the player, the adaptive-bitrate and prebuffering strategy, the cache policy, and mid-play network recovery so the experience stays smooth on a flaky connection?
Design a video playback screen for an iOS app that streams long-form video. Requirements: use adaptive bitrate over the streaming protocol HLS so quality tracks the available bandwidth without the user switching manually; prebuffer enough ahead that playback starts fast and rarely stalls; apply a caching policy so replays and short seeks do not refetch from the network; and handle the network dropping mid-play gracefully — keep playing to the buffered edge, show a buffering state, and resume from the same position when the connection returns, without restarting the video from the beginning. How do you structure the player, the adaptive-bitrate and prebuffering strategy, the cache policy, and mid-play network recovery so the experience stays smooth on a flaky connection?
Play an HLS stream through AVPlayer and let its adaptive-bitrate logic pick a variant from measured bandwidth, prebuffering a few seconds ahead so playback starts fast. Cache recent segments so short seeks and replays skip the network. On a mid-play drop, keep playing to the buffered edge, show a buffering state, and resume from the same position when connectivity returns rather than restarting the video.
Common mistakes
- ✗Downloading a fixed-quality file instead of adapting bitrate to bandwidth
- ✗Restarting the video from the start after a mid-play drop instead of resuming
- ✗Caching nothing, so every short seek or replay refetches from the network
Follow-up questions
- →How does HLS switch bitrate at a segment boundary without a visible stall?
- →How much prebuffer balances a fast start against data wasted on an early drop?
SeniorDesignRareDesign the main screen of a flight-ticket app. Two From/To fields show city autocomplete as the user types. Picking a From city drops a marker on an interactive world map and shows markers for every destination reachable from it, each labelled with the minimum ticket price (which changes over time). Tapping a marker opens the cheapest flight plus an 'Others' list. The From field should prefill from device geolocation, and the backend API is still in progress so the candidate must not depend on it. A key complication: when many markers overlap in the visible map viewport, hide the less-popular ones — only the most popular non-overlapping markers stay visible, recomputed on pan/zoom. The app must also keep working over an unstable connection. How do you structure the screen, the marker-overlap/visibility algorithm, autocomplete, geolocation prefill, and offline behaviour?
Design the main screen of a flight-ticket app. Two From/To fields show city autocomplete as the user types. Picking a From city drops a marker on an interactive world map and shows markers for every destination reachable from it, each labelled with the minimum ticket price (which changes over time). Tapping a marker opens the cheapest flight plus an 'Others' list. The From field should prefill from device geolocation, and the backend API is still in progress so the candidate must not depend on it. A key complication: when many markers overlap in the visible map viewport, hide the less-popular ones — only the most popular non-overlapping markers stay visible, recomputed on pan/zoom. The app must also keep working over an unstable connection. How do you structure the screen, the marker-overlap/visibility algorithm, autocomplete, geolocation prefill, and offline behaviour?
For marker visibility, sort destinations by popularity descending and greedily place markers within the current viewport — skip any whose footprint overlaps an already-placed (more popular) marker. Recompute on every pan/zoom and on price updates. Debounce autocomplete and cache city lists; prefill From via CoreLocation. Persist the last destination/price set so the map and markers still render offline, refreshing prices when the connection returns.
Common mistakes
- ✗Resolving marker overlap without sorting by popularity first
- ✗Recomputing visibility once instead of on every pan/zoom and price change
- ✗Firing an autocomplete request per keystroke with no debounce or caching
Follow-up questions
- →How do you keep the greedy overlap pass fast enough to run on every pan/zoom?
- →How do live price updates reach already-rendered markers without a full reload?