“Build a notification inbox” is a frontend interview prompt that overlaps with comments, message threads, and feeds — but with its own twists around grouping, read state, and the bell-icon UX. Used in GitHub, Linear, Slack, and most SaaS products. The interview tests whether you can model the data, render efficiently, and handle the cross-cutting concerns of read/unread state.
Clarify scope
- One unified inbox or separated by category (mentions, replies, system)?
- Group similar notifications (“Alice and 5 others reacted”)?
- Mark-as-read individually or per-thread?
- Real-time updates while open?
- Mobile parity?
The data model
{
id: "n1",
type: "comment_reply",
actor: { id, name, avatar },
target: { type: "issue", id, title, url },
read: false,
createdAt: "...",
groupKey: "issue-42-comments", // for grouping
body: "...optional preview..."
}
Each notification references an actor (who did it), a target (what it is about), and a type (template for rendering). Group key lets you collapse related notifications.
Grouping
Two approaches:
- Server-side grouping: backend returns a single “Alice and 5 others commented” with the underlying notification IDs. Cleaner, but server has to know your UX rules.
- Client-side grouping: backend returns raw notifications; client groups by key in memory. More flexible; requires careful state management.
Most modern systems do client-side grouping for flexibility. Group by (groupKey, day) typically; collapse until expanded.
Read state — the senior signal
- Per-notification read state, persisted server-side
- Optimistic update on mark-as-read; reconcile on server response
- “Mark all as read” must be efficient — single server call, batch update
- Unread count must stay in sync across devices via the WebSocket subscription
- Distinguish “read” from “seen” — opening the inbox marks-as-seen; clicking marks-as-read
The bell icon
- Badge count = unread notifications
- Update reactively to incoming notifications
- Pulse animation on new arrival (subtle; not Slack-loud)
- Click opens the panel; clicks outside close it
- Keyboard: Cmd-K or N opens; Escape closes
Real-time updates
- WebSocket subscription pushes new notifications
- Insert at top of list with subtle animation
- Bump unread count optimistically
- Group with existing notifications if same group key
- If panel is closed, just bump the bell badge
Infinite scroll
- Cursor-based pagination — load 20 at a time
- Sentinel div + Intersection Observer triggers next page
- Show skeleton or “Loading…” at the bottom during fetch
- “You’re all caught up!” footer when no more notifications
Filtering and tabs
- “All”, “Mentions”, “Unread”, custom filters
- Persist active tab in URL or localStorage
- Counts per tab updated reactively
Mobile considerations
- Bottom sheet or full screen, not a popover (popover hides keyboard awkwardly)
- Swipe-to-dismiss or swipe-to-archive on each notification
- Pull-to-refresh
Accessibility
- Bell icon has
aria-label="Notifications, N unread" - Panel uses
role="dialog"with focus trap when open - List uses
role="feed"for screen reader semantics; each item is anarticle - Live region announces new notifications: “New notification from Alice”
- Keyboard navigation: arrow keys move between items, Enter opens, Cmd-A marks all read
Performance
- Memoize notification components by ID
- Virtualize for users with 1000+ notifications (rare but real)
- Debounce read-state updates (every 500ms instead of every action)
- Avoid re-rendering the whole list on bell-badge change
Edge cases interviewers love
- Notification arrives while user is mid-interaction with another notification — do not steal focus
- Notification refers to a deleted resource (comment removed) — show graceful tombstone
- User receives 100 notifications in a minute — group aggressively, do not animate every arrival
- Cross-device read state — opens panel on phone, reads, opens on desktop minutes later — should reflect read state
What separates senior from staff
Senior candidates handle real-time updates and read state correctly. Staff candidates discuss the grouping data model, the seen/read distinction, and cross-device sync. Principal candidates raise the privacy story (which actions warrant a notification, who controls preferences).
Frequently Asked Questions
Library options for production?
Knock and Magic Bell are commercial notification platforms with embeddable inbox UIs. For self-built, the architecture above is standard.
How do I handle email + push + in-app coherently?
Server holds the canonical notification record; channels are derived (email if not seen in 5 min, push if mobile-only). Read state syncs across all surfaces.
What about notification preferences?
Per-category (mentions, replies, follows, system) opt-out preferences. Surface in settings. Server respects when generating; client should not have to filter post-hoc.