Developing a Likes System in a Mobile App
A like seems trivial: tap — increment counter — icon filled. But without optimistic update the button "lags" 300-500ms waiting for server response, subjectively killing the app feel. And double-tap or quick repeated presses without debounce generate extra requests and can break the counter.
Optimistic Update
Standard for social apps — update UI instantly without waiting for server:
iOS (UIKit):
func toggleLike(for post: Post) {
let wasLiked = post.isLiked
// Update UI instantly
post.isLiked = !wasLiked
post.likesCount += wasLiked ? -1 : 1
updateCell(for: post)
// Server request
apiService.toggleLike(postId: post.id) { [weak self] result in
if case .failure = result {
// Rollback
post.isLiked = wasLiked
post.likesCount += wasLiked ? 1 : -1
self?.updateCell(for: post)
}
}
}
On Compose similarly: likedState in ViewModel changes immediately, request goes in parallel, on error — StateFlow rolls back to previous value.
Debounce and Spam Protection
Fast double-presses need protection. Simplest way — isRequesting: Bool flag on ViewModel level, blocking repeat call until response received. For more complex cases — debounce on 300ms: send final state (liked/unliked), not each press.
On Android with Kotlin Flow:
likeButtonClicks
.debounce(300)
.distinctUntilChanged()
.flatMapLatest { liked -> toggleLikeUseCase(postId, liked) }
.launchIn(viewModelScope)
Animation
Like animation — detail users notice. Instagram approach: heart "springs" on tap. On iOS — UIView.animate(withDuration: 0.1, animations: { button.transform = CGAffineTransform(scaleX: 1.3, y: 1.3) }) { _ in UIView.animate(...) { button.transform = .identity } }. On Compose — animateFloatAsState with spring(dampingRatio = 0.4f).
Filled like color via tintColor (iOS) or ColorFilter.tint (Compose). Icon — SF Symbol heart / heart.fill on iOS, Material Icon on Android.
Counter and Aggregation
Store likes_count as denormalized field in post table — correct. Don't count SELECT COUNT(*) on every feed request. Increment/decrement via atomic UPDATE posts SET likes_count = likes_count + 1 WHERE id = ? — without race condition.
Like uniqueness: table likes (user_id, post_id, PRIMARY KEY (user_id, post_id)). Duplicates impossible at DB level.
Timeline
Basic implementation with optimistic update and animation — 4 hours to 1 day depending on platform. Cost calculated individually.







