Design an Instagram-like Feed
Published:
๐ฏ Problem Statement
Design the architecture for an Instagram-like feed for iOS that can:
- Display images and videos efficiently
- Support infinite scrolling with pagination
- Handle offline mode and caching
- Optimize for battery life and data usage
- Scale to millions of users
Constraints:
- Feed has millions of posts
- Images range from 500KB to 5MB
- Videos up to 60 seconds, 1080p
- Users scroll through 50+ posts per session
- Must work on slow 3G networks
๐ STEP 1: High-Level Architecture Diagram
In the interview, start by drawing this on the whiteboard:
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ iOS CLIENT LAYER โ
โ โ
โ โโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโ โ
โ โ FeedView โโโโโโโโโโถโ FeedViewModelโ โ
โ โ Controller โโโโโโโโโโโ โ โ
โ โ โ โ @Published โ โ
โ โ - UITable/ โ โ properties โ โ
โ โ Collectionโ โโโโโโโโฌโโโโโโโโ โ
โ โ - Cells โ โ โ
โ โ - Prefetch โ โ โ
โ โโโโโโโโโโโโโโโโ โ โ
โ โ โ
โ โโโโโโโโโโผโโโโโโโโโโ โ
โ โ FeedRepository โ โ
โ โ (Single Source โ โ
โ โ of Truth) โ โ
โ โโโโโโโโโโโฌโโโโโโโโโ โ
โ โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโ โ
โ โ โ โ โ
โ โโโโโโโโผโโโโโโโ โโโโโโโโโโผโโโโโโโโ โโโโโโโโผโโโโโโ
โ โ Network โ โ Cache โ โ Database โโ
โ โ Service โ โ Manager โ โ Manager โโ
โ โ โ โ โ โ โโ
โ โ - API calls โ โ - Memory cache โ โ - CoreDataโโ
โ โ - URLSessionโ โ - Disk cache โ โ - Offline โโ
โ โโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโ
โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โ HTTP/HTTPS
โผ
โโโโโโโโโโโโโโโโโโโโ
โ BACKEND API โ
โ โ
โ GET /feed โ
โ GET /post/:id โ
โโโโโโโโโโโโโโโโโโโโ
โ
โผ
โโโโโโโโโโโโโโโโโโโโ
โ CDN (Images) โ
โ - CloudFront โ
โ - Imgix โ
โโโโโโโโโโโโโโโโโโโโ
Key Dependencies:
- ViewController depends on ViewModel (observer pattern)
- ViewModel depends on Repository (clean architecture)
- Repository coordinates Network, Cache, Database
- All image/video URLs point to CDN
๐ฌ What to say while drawing:
โIโll start with a layered architecture. At the top, we have the View layer handling UI. The ViewModel manages state and business logic. The Repository acts as a single source of truth, coordinating between network, cache, and database. For images, weโll use a CDN to reduce latency and server load.โ
๐ค STEP 2: User Flow Diagram
Draw this to show interaction flow:
USER OPENS APP
โ
โผ
โโโโโโโโโโโโโโโโโโโ
โ 1. Load Feed โ
โ Screen โ
โโโโโโโโโโฌโโโโโโโโโ
โ
โผ
โโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโ
โ 2. Check โโโโโโโโโโถโ YES: Show โ
โ Cache? โ โ Cached โ
โ โ โ Posts โ
โโโโโโโโโโฌโโโโโโโโโ โโโโโโโโโโโโโโโโ
โ NO
โผ
โโโโโโโโโโโโโโโโโโโ
โ 3. Show โ
โ Loading โ
โ Indicator โ
โโโโโโโโโโฌโโโโโโโโโ
โ
โผ
โโโโโโโโโโโโโโโโโโโ
โ 4. Fetch 20 โ
โ Posts from โ
โ API โ
โโโโโโโโโโฌโโโโโโโโโ
โ
โผ
โโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโ
โ 5. Parse JSON โโโโโโโโโโถโ 6. Store in โ
โ Response โ โ Cache โ
โโโโโโโโโโฌโโโโโโโโโ โโโโโโโโโโโโโโโโ
โ
โผ
โโโโโโโโโโโโโโโโโโโ
โ 7. Display โ
โ Posts โ
โ (Text only) โ
โโโโโโโโโโฌโโโโโโโโโ
โ
โผ
โโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโ
โ 8. Download โโโโโโโโโโถโ 9. Show โ
โ Images โ โ Images โ
โ Async โ โ as loaded โ
โโโโโโโโโโฌโโโโโโโโโ โโโโโโโโโโโโโโโโ
โ
โ USER SCROLLS DOWN
โผ
โโโโโโโโโโโโโโโโโโโ
โ 10. Reached โ
โ 80% of list โ
โโโโโโโโโโฌโโโโโโโโโ
โ
โผ
โโโโโโโโโโโโโโโโโโโ
โ 11. Prefetch โ
โ next 20 โ
โ posts โ
โโโโโโโโโโฌโโโโโโโโโ
โ
โผ
โโโโโโโโโโโโโโโโโโโ
โ 12. Repeat โ
โ steps 4-9 โ
โโโโโโโโโโโโโโโโโโโ
OFFLINE MODE:
โโโโโโโโโโโโโโโโโโโ
โ No network? โโโโโโโโโโถโ Show cached โ
โ โ โ posts with โ
โ โ โ banner โ
โโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโ
๐ฌ What to say while drawing:
โWhen the user opens the app, we first check the cache. If we have data, show it immediately for perceived performance. Then fetch fresh data in the background. As users scroll, we prefetch the next page at 80% scroll position. For offline mode, we gracefully degrade by showing cached content with a banner notification.โ
๐ STEP 3: Detailed Component Interactions
Sequence diagram for loading a post:
User ViewController ViewModel Repository NetworkService CacheManager
โ โ โ โ โ โ
โ Scroll โ โ โ โ โ
โโโโโโโโโโโโโโโโโโถโ โ โ โ โ
โ โ loadMore() โ โ โ โ
โ โโโโโโโโโโโโโโโถโ โ โ โ
โ โ โfetchPosts()โ โ โ
โ โ โโโโโโโโโโโโโถโ โ โ
โ โ โ โ check cache โ โ
โ โ โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโถโ
โ โ โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ โ โ โ MISS โ โ
โ โ โ โ โ โ
โ โ โ โ GET /feed โ โ
โ โ โ โโโโโโโโโโโโโโโโถโ โ
โ โ โ โ โ โ
โ โ โ โ JSON array โ โ
โ โ โ โโโโโโโโโโโโโโโโโ โ
โ โ โ โ โ โ
โ โ โ โ store cache โ โ
โ โ โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโถโ
โ โ โ โ โ โ
โ โ โ [Post] โ โ โ
โ โ โโโโโโโโโโโโโโ โ โ
โ โ posts array โ โ โ โ
โ โโโโโโโโโโโโโโโโ โ โ โ
โ Display posts โ โ โ โ โ
โโโโโโโโโโโโโโโโโโโ โ โ โ โ
๐ Key Considerations
1. Memory Management
Problem: Loading hundreds of high-res images will cause OOM crashes.
Solutions:
- Use
NSCache
for memory cache (auto-evicts when low memory) - Downscale images to display size before caching
- Implement cell reuse properly
- Release off-screen images aggressively
2. Prefetching Strategy
Question: When and how much should you prefetch?
Approach:
- Use
UITableViewDataSourcePrefetching
/UICollectionViewDataSourcePrefetching
- Prefetch 5-10 items ahead of visible area
- Cancel prefetch requests when scrolling direction changes
- Prioritize visible items over prefetch items
3. Caching Strategy
Two-tier caching:
Cache Type | Size | Eviction Policy | Use Case |
---|---|---|---|
Memory | 100-200 MB | LRU (Least Recently Used) | Fast access |
Disk | 500 MB - 1 GB | TTL + LRU | Offline support |
4. Video Playback
Challenges:
- Battery drain from autoplay
- Network bandwidth
- Memory from multiple players
Solutions:
- AVPlayer pool (reuse 3-5 instances)
- Pause off-screen videos immediately
- Preload only visible +1 video
- Use HLS for adaptive streaming
5. Network Optimization
- Batch API requests (fetch 20-30 posts at once)
- Use CDN URLs for media
- Implement retry logic with exponential backoff
- Support resume for failed downloads
โ Proposed Architecture
High-Level Components
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ ViewController Layer โ
โ (FeedViewController + Cells) โ
โโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโ
โ ViewModel Layer โ
โ (FeedViewModel + State Management) โ
โโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Repository Pattern โ
โ (FeedRepository - Single Source) โ
โโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโ
โ โ
โโโโโโโผโโโโโโโ โโโโโโโโโโผโโโโโโโโโโ
โ Network โ โ Local Storage โ
โ Service โ โ (Core Data) โ
โโโโโโโฌโโโโโโโ โโโโโโโโโโฌโโโโโโโโโโ
โ โ
โโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโ
โ Cache Manager โ
โ (Memory + Disk Image Cache) โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Detailed Architecture
1. ViewModel (MVVM Pattern)
class FeedViewModel {
// Observable state
@Published var posts: [FeedPost] = []
@Published var isLoading = false
@Published var error: Error?
private let repository: FeedRepository
private var cancellables = Set<AnyCancellable>()
private var currentPage = 0
private var hasMorePages = true
func loadInitialFeed() {
currentPage = 0
repository.fetchFeed(page: 0)
.sink(
receiveCompletion: { [weak self] completion in
self?.isLoading = false
if case .failure(let error) = completion {
self?.error = error
}
},
receiveValue: { [weak self] posts in
self?.posts = posts
self?.hasMorePages = !posts.isEmpty
}
)
.store(in: &cancellables)
}
func loadMore() {
guard !isLoading && hasMorePages else { return }
currentPage += 1
repository.fetchFeed(page: currentPage)
.sink { [weak self] posts in
self?.posts.append(contentsOf: posts)
self?.hasMorePages = !posts.isEmpty
}
.store(in: &cancellables)
}
}
2. Repository Pattern
class FeedRepository {
private let networkService: NetworkService
private let localStorage: LocalStorage
private let imageCache: ImageCacheManager
func fetchFeed(page: Int) -> AnyPublisher<[FeedPost], Error> {
// Try local first if offline
if !Reachability.isConnected {
return localStorage.fetchCachedFeed()
}
// Fetch from network
return networkService.getFeed(page: page)
.handleEvents(receiveOutput: { [weak self] posts in
// Cache to local storage
self?.localStorage.save(posts)
})
.eraseToAnyPublisher()
}
}
3. Image Cache Manager
class ImageCacheManager {
private let memoryCache = NSCache<NSString, UIImage>()
private let diskCache: DiskCache
private let downloadQueue: OperationQueue
init() {
// Configure memory cache
memoryCache.countLimit = 100
memoryCache.totalCostLimit = 200 * 1024 * 1024 // 200 MB
// Configure download queue
downloadQueue.maxConcurrentOperationCount = 4
}
func loadImage(url: URL, size: CGSize, completion: @escaping (UIImage?) -> Void) {
let cacheKey = url.absoluteString as NSString
// Check memory cache
if let cachedImage = memoryCache.object(forKey: cacheKey) {
completion(cachedImage)
return
}
// Check disk cache
diskCache.image(forKey: cacheKey as String) { [weak self] diskImage in
if let diskImage = diskImage {
self?.memoryCache.setObject(diskImage, forKey: cacheKey)
completion(diskImage)
return
}
// Download
self?.downloadImage(url: url, size: size, cacheKey: cacheKey, completion: completion)
}
}
private func downloadImage(url: URL, size: CGSize, cacheKey: NSString, completion: @escaping (UIImage?) -> Void) {
downloadQueue.addOperation {
guard let data = try? Data(contentsOf: url),
var image = UIImage(data: data) else {
completion(nil)
return
}
// Downscale to display size
image = image.downscaled(to: size)
// Cache both memory and disk
self.memoryCache.setObject(image, forKey: cacheKey)
self.diskCache.store(image, forKey: cacheKey as String)
DispatchQueue.main.async {
completion(image)
}
}
}
}
4. Prefetching
extension FeedViewController: UITableViewDataSourcePrefetching {
func tableView(_ tableView: UITableView, prefetchRowsAt indexPaths: [IndexPath]) {
for indexPath in indexPaths {
let post = viewModel.posts[indexPath.row]
// Prefetch image
imageCache.loadImage(url: post.imageURL, size: cellSize) { _ in }
// Trigger load more if near end
if indexPath.row >= viewModel.posts.count - 5 {
viewModel.loadMore()
}
}
}
func tableView(_ tableView: UITableView, cancelPrefetchingForRowsAt indexPaths: [IndexPath]) {
// Cancel prefetch operations for off-screen rows
for indexPath in indexPaths {
let post = viewModel.posts[indexPath.row]
imageCache.cancelLoad(url: post.imageURL)
}
}
}
๐ก What Interviewers Want to Hear
Trade-offs Discussion:
Memory vs Network:
- Large memory cache = faster but more crashes
- Small memory cache = slower but safer
- Balance: 100-200 MB memory, rest on disk
Prefetching vs Bandwidth:
- Aggressive prefetching = smooth UX, wastes data
- No prefetching = janky scrolling
- Balance: Prefetch 5-10 ahead, cancel on direction change
UITableView vs UICollectionView:
- TableView: Simpler, better for vertical lists
- CollectionView: More flexible, better for grids
- Instagram uses: UICollectionView (for flexibility)
Performance Optimizations:
- Image Decoding on Background Thread:
DispatchQueue.global(qos: .userInitiated).async { let decodedImage = image.decodedImage() DispatchQueue.main.async { cell.imageView.image = decodedImage } }
- Cell Height Caching: ```swift var heightCache: [IndexPath: CGFloat] = [:]
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat { return heightCache[indexPath] ?? 300 }
3. **Lazy Loading:**
```swift
// Only load images when cell becomes visible
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
guard let feedCell = cell as? FeedCell else { return }
feedCell.loadImage()
}
๐ Advanced Topics
Offline Support
class OfflineFeedManager {
func syncWhenOnline() {
NotificationCenter.default.publisher(for: .reachabilityChanged)
.filter { $0.userInfo?["isReachable"] as? Bool == true }
.sink { [weak self] _ in
self?.syncPendingActions()
self?.refreshFeed()
}
.store(in: &cancellables)
}
}
Video Optimization
class VideoPlayerPool {
private var players: [AVPlayer] = []
private let maxPlayers = 3
func getPlayer() -> AVPlayer {
if players.count < maxPlayers {
let player = AVPlayer()
players.append(player)
return player
}
// Reuse least recently used player
return players.removeFirst()
}
func pauseAllPlayers() {
players.forEach { $0.pause() }
}
}
๐ฏ Summary Checklist
When answering this question, make sure to cover:
- Architecture pattern (MVVM, Repository)
- Memory management (NSCache, downscaling)
- Caching strategy (2-tier: memory + disk)
- Prefetching (UITableViewDataSourcePrefetching)
- Pagination (cursor-based or offset)
- Offline support (Core Data persistence)
- Video handling (AVPlayer pool, autoplay strategy)
- Performance (background decoding, cell reuse)
- Network (batching, retry logic, CDN)
- Trade-offs (discuss pros/cons of each choice)
๐ก Pro Tip: Start with high-level architecture, then drill down into specific components based on interviewerโs focus. Always discuss trade-offs and explain your reasoning!
Share on
Twitter Facebook LinkedInโ Buy me a coffee! ๐
If you found this article helpful, consider buying me a coffee to support my work! ๐