Hey folks, Rahul here ๐
Every app has notifications. Most implement them badly โ a polling loop that hammers the server, an unread count that's always wrong, and grouping logic that shows "John and 47 others liked your post" as 48 separate items. Sound familiar?
Let's design a notification system that handles real-time delivery, intelligent grouping, cross-tab synchronization, and the deceptively complex read/unread state machine.
R โ Requirements
Functional Requirements
- Display in-app notification bell with unread count badge
- Notification dropdown/panel with grouped notifications
- Mark individual or all notifications as read
- Real-time delivery โ new notifications appear without refresh
- Push notifications (browser) with user opt-in
- Notification preferences (per-category enable/disable)
- Click-to-navigate: each notification links to relevant content
Non-Functional Requirements
- Real-time: <2s delivery from event to UI
- Consistency: Unread count must be accurate across tabs
- Performance: Support users with 10K+ notifications
- Battery: Mobile-friendly โ no aggressive polling
- Graceful degradation: Works without WebSocket (falls back to polling)
A โ Architecture
Delivery Strategies
| Strategy | Latency | Battery | Complexity | Use When |
|---|---|---|---|---|
| Polling | High (interval) | Poor | Low | Fallback only |
| Long Polling | Medium | OK | Medium | SSE unavailable |
| SSE (Server-Sent Events) | Low | Good | Medium | Read-only stream โ |
| WebSocket | Lowest | Good | High | Already have WS for other features |
Recommendation: SSE for notifications specifically. It's simpler than WebSocket, works over HTTP/2, auto-reconnects, and notifications are inherently server โ client (no bidirectional need).
Component Architecture
Notification Flow
D โ Data Model
Notification Types and Grouping
Client State
Grouping Logic
I โ Interface Definition
SSE Connection
Mark as Read โ Optimistic with Batching
O โ Optimizations
1. Cross-Tab Synchronization
2. Unread Count Badge Animation
3. Browser Push Notifications
4. Notification Preferences
Production Gotchas Rahul Has Debugged ๐ฅ
- Unread Count Drift: If a notification is created between the initial fetch and SSE connection, you miss it. Solution: SSE connection sends a
count_syncevent every 60s to reconcile. - Self-Notifications: Don't notify users about their own actions. The backend must filter
actor_id !== recipient_idโ never rely on the frontend for this. - Notification Storms: A viral post generates thousands of likes. Without grouping, the user gets 1,000 notifications. Backend should coalesce within a time window (e.g., group all likes within 5 minutes into one notification).
- SSE Memory Leak:
EventSourceaccumulates all messages in memory if you don't handle them. Always consume events promptly. Some browsers also leak if you create/destroy EventSource instances rapidly. - Mobile Tab Backgrounding: iOS Safari kills SSE connections when the tab is backgrounded. On
visibilitychangeโ visible, reconnect and fetch missed notifications sincelastEventId.
Next up: #11: Design a Drag-and-Drop Kanban Board โ reorder algorithms, optimistic multi-list moves, and the fractional indexing trick. ๐