Back to Machine Coding
Machine Coding
easy
mid

How would you build a priority based modal system?

Extends the modal manager: each enqueued modal has a priority (number or enum). Modals queue but only the highest-priority one is shown; a new higher-priority modal preempts the current one (or stacks on top depending on policy). Useful for cohesive UX when multiple systems want to show modals (auth, billing, onboarding, errors).

4 min read·~35 min to think through

A priority-based modal system avoids the bug where three independent systems each open() a modal and the user sees a stack of unrelated dialogs. It coordinates which modal is visible right now based on priority and policy.

1. The shape of the problem

Multiple unrelated callers want to show modals:

  • Onboarding tooltip.
  • Billing "card expiring" warning.
  • Auth re-login prompt.
  • Critical error.

Without coordination: the user gets all four stacked. With priority: only the most important shows now; the rest queue and surface as the user dismisses each.

2. Priority model

js
const Priority = { LOW: 0, NORMAL: 1, HIGH: 2, CRITICAL: 3 };

Or just numbers. Document the meaning.

3. The store

js
class ModalQueue {
  constructor() {
    this.queue = [];           // pending: { id, priority, content, options }
    this.current = null;       // currently shown
    this.subscribers = new Set();
  }

  open({ content, priority = 0, options = {} }) {
    const id = crypto.randomUUID();
    const item = { id, content, priority, options };

    // Decide policy: preempt or queue
    if (!this.current) {
      this.current = item;
    } else if (priority > this.current.priority) {
      this.queue.push(this.current);    // demote current
      this.current = item;
    } else {
      this.queue.push(item);
    }

    this.queue.sort((a, b) => b.priority - a.priority);
    this._notify();
    return id;
  }

  dismiss(id) {
    if (this.current?.id === id) {
      this.current = this.queue.shift() ?? null;
    } else {
      this.queue = this.queue.filter((q) => q.id !== id);
    }
    this._notify();
  }

  subscribe(fn) { this.subscribers.add(fn); return () => this.subscribers.delete(fn); }
  _notify() { for (const fn of this.subscribers) fn(this.current); }
}

4. Two policies — preempt vs stack

  • Preempt (above): higher priority replaces the current modal; the previous one re-queues for later.
  • Stack alternative: higher priority renders on top; the previous stays underneath. Use when the lower modal is contextually important.

Make it an option per open:

js
open({ ..., policy: "preempt" | "stack" })

5. React integration

jsx
const ModalCtx = createContext(null);
const queue = new ModalQueue();

export function ModalProvider({ children }) {
  const [current, setCurrent] = useState(queue.current);
  useEffect(() => queue.subscribe(setCurrent), []);
  return (
    <ModalCtx.Provider value={queue}>
      {children}
      {current && <ModalShell onClose={() => queue.dismiss(current.id)}>{current.content}</ModalShell>}
    </ModalCtx.Provider>
  );
}

6. Rules for callers

  • Pick a priority deliberately, not "always HIGH".
  • CRITICAL is for things that block the user from continuing safely (session expired, payment failed during checkout).
  • Avoid showing low-priority modals after route changes — they'll feel disconnected.

7. Extras

  • Suppress while user is in a flow (e.g., typing in a form) — defer non-CRITICAL modals.
  • Dedupe — if the same modal is opened twice, ignore the second.
  • Expiry — drop low-priority modals from the queue after N minutes.
  • Logging — record which modals queued behind which, helps detect modal spam.

8. Accessibility / UX

  • Focus management when a modal preempts another — return focus to the trigger of whichever closes last.
  • Don't auto-dismiss a low-priority modal that was visible just because a higher one arrives — re-queue it.
  • Announce the new modal via the dialog ARIA roles (the dialog itself handles this — see [[build-a-modal-management-system]]).

Interview framing

"Wrap the modal store with a priority queue. open checks the current modal's priority: higher priority preempts (push current back into the queue, sorted), equal-or-lower queues. Dismiss promotes the next-highest queued modal to current. Make the policy (preempt vs stack on top) an option. Add dedupe, suppression during user flows, and expiry for low-priority items. The point is coordination — without it, every independent system opens its own modal and the user sees four unrelated dialogs at once."

Follow-up questions

  • Preempt vs stack — when do you choose each?
  • How do you prevent CRITICAL-modal abuse?
  • How does this integrate with the basic modal manager?
  • What's the right deduplication strategy?

Common mistakes

  • Showing every modal opened, regardless of context.
  • No re-queue when preempted — modal is lost.
  • Treating priority as a quality signal — everyone marks theirs HIGH.
  • Allowing low-priority modals to interrupt critical user flows.

Performance considerations

  • Tiny queue; sort cost is negligible. Subscribe/unsubscribe correctly to avoid leaks.

Edge cases

  • Two CRITICAL modals open simultaneously.
  • Preemption mid-animation.
  • Page navigation with a queued modal (drop or persist?).
  • Stale queued modals (expire after N min).

Real-world examples

  • Onboarding tour, billing dunning, system-wide notifications, A/B-test prompts.

Senior engineer discussion

Seniors recognize this as a *coordination* problem (not a UI problem), establish clear priority semantics, choose preempt vs stack per caller, suppress non-critical modals during sensitive user flows, and log queueing behavior to detect modal spam in the wild.

Related questions