Skip to the solution
EasyMachine Coding10 min read

Build a Notification Badge

1.2k views

The problem

Question presented to candidate: "Build the notification bell for our app header. It shows a red badge with the number of unread notifications, shows 99+ when there are more than 99, and disappears when everything is read. Clicking the bell opens a panel listing recent notifications; clicking one marks it read, and there is a Mark all as read button. Add buttons to simulate new notifications arriving."

Requirements:

  • A bell button with a badge: the unread count, capped at 99+, hidden at zero.
  • A panel (toggled by the bell) listing the newest 8 notifications, unread ones highlighted.
  • Clicking a notification marks it read; Mark all as read clears the badge.
  • Escape and clicking outside close the panel; Escape returns focus to the bell.
  • Screen reader users hear the real count, and are told when new notifications arrive.

What a strong answer should cover:

  • The notification list is the state; the unread count and the badge label are derived from it, never stored separately.
  • Capping is a display concern: the label says 99+ while the accessible name keeps the exact number.
  • Conditional rendering of the badge at zero, and aria-hidden on the visual badge so it is not read twice.
  • Disclosure semantics on the bell (aria-expanded, aria-controls), Escape and outside-click handling with listener cleanup.
  • Immutable updates when marking items read, and where the list lives (the parent, standing in for a server push).

Clarifying questions expected:

  • "Where do notifications come from: polling, WebSocket, or server-sent events?"
  • "Is the cap always 99, and should a zero state show a dot or nothing?"
  • "Does opening the panel mark everything as seen, or only clicking an item?"

Code / implementation expected: Yes. A bell component with a derived badge and a panel, plus a parent that simulates incoming notifications.

  • React
  • UI
  • Badge

Try it yourself

Sketch your approach before you read the solution. That is what the interview tests.

Stuck? Get a hint.

A short nudge toward the approach. It will not give the answer away.

Solution

The solution is hidden so you can try first

10 min read ยท 9 sections

React solution

Every file of the working solution. Open it in the playground to run and change it.

ReactComplete solution
import { useRef, useState } from "react";
import NotificationBell from "./src/components/NotificationBell";

const initial = [
  { id: 3, text: "Sam commented on your post", read: false },
  { id: 2, text: "Your order has shipped", read: false },
  { id: 1, text: "Welcome to the app", read: true },
];

export default function App() {
  const [notifications, setNotifications] = useState(initial);
  const [announcement, setAnnouncement] = useState("");
  const nextId = useRef(4);

  // Simulates notifications pushed by a server (there is no network here).
  function simulate(count) {
    const fresh = Array.from({ length: count }, () => {
      const id = nextId.current++;
      return { id, text: "New notification #" + id, read: false };
    }).reverse(); // newest first; done here, never inside the updater
    setNotifications((list) => [...fresh, ...list]);
    setAnnouncement(
      count === 1 ? "1 new notification" : count + " new notifications",
    );
  }

  const markRead = (id) =>
    setNotifications((list) =>
      list.map((n) => (n.id === id ? { ...n, read: true } : n)),
    );
  const markAllRead = () =>
    setNotifications((list) => list.map((n) => ({ ...n, read: true })));

  return (
    <main className="app">
      <header className="bar">
        <span className="brand">Inbox</span>
        <NotificationBell
          notifications={notifications}
          max={99}
          onMarkRead={markRead}
          onMarkAllRead={markAllRead}
        />
      </header>
      <div className="controls">
        <button type="button" data-testid="add-1" onClick={() => simulate(1)}>
          Simulate 1 new
        </button>
        <button
          type="button"
          data-testid="add-120"
          onClick={() => simulate(120)}
        >
          Simulate 120 new
        </button>
      </div>
      <p className="sr-only" aria-live="polite" data-testid="live">
        {announcement}
      </p>
    </main>
  );
}

Discussion

Other approaches, follow-ups and how it went in real interviews.

Sign in to join the discussion.

No comments yet. Share how you would answer it.

Question 1 of 107 in Machine coding

Keep your momentum going.

Mark this question solved to track your progress, then take the next one while it is fresh.

Up nextEasy

Build an Avatar Group

Continue