Requirements exploration
In this step, the interviewer wants to see if you can clearly describe what a global toast system should do before you jump into components or code. You should sound like someone who has used and built these systems before: you talk about triggering toasts from anywhere, stacking, timers, accessibility, and placement.
**What you’re solving:**
You need a global notification system that lets any part of the app call something like `toast.success("Saved")` or `toast.error("Failed")` and shows small non-blocking messages on top of the UI.
It should support different variants (success, error, warning, info), auto-dismiss after a delay, allow manual close, and stack multiple toasts without breaking the layout.
**What the interviewer looks for:**
- You understand that this is a **global concern**, not a local component.
- You talk about **stacking multiple toasts** and what happens when many are fired.
- You mention **auto-dismiss timers** and how they are cleaned up.
- You are aware of **z-index / layering** over the rest of the UI.
- You bring up **accessibility** (ARIA roles, announcements, focus) and **responsive placement**.
How you should act here
Start by restating the problem in your own words: "We want a global toast API like `toast.success()` that shows short-lived notifications, stacks them nicely, and cleans up timers correctly." Then move into clarifying questions instead of jumping straight into implementation details.
User flow you should describe
1
1. Trigger
Some part of the app calls `toast.success("Profile saved")` after an action completes.
→
2
2. Show toast
A new toast appears in the chosen corner (e.g. top-right), with the correct style and icon for its type.
→
3
3. Lifetime
A timer starts for auto-dismiss. The user can also close it manually (close button or click).
→
4
4. Stack behavior
If more toasts appear, they stack in a consistent order (e.g. newest on top). Old ones disappear as timers finish or the user closes them.
→
5
5. Edge cases
On small screens, the layout adapts (e.g. full-width at the top/bottom). Screen readers get the message, and timers are cleaned up on unmount or route changes.
Smart clarifying questions you should ask
- From where can toasts be triggered? Only from React components, or also from plain JS utilities / services?
- Do we need different variants (success, error, warning, info) with different icons and colors?
- What is the default auto-dismiss timeout? Can it be customized per toast?
- How many toasts can be visible at the same time before we start removing or queuing them?
- Which placements do we need to support (top-right, top-center, bottom-right, bottom-left, mobile full-width)?
- Do we need actions inside toasts (e.g. an "Undo" button) that require focus and keyboard support?
What you should explicitly confirm
- The toast system is **global** and should not require prop-drilling.
- Toasts are **non-blocking**: they should not stop the user from interacting with the page.
- Each toast has a **type**, **message**, and **lifetime** (auto-dismiss + manual close).
- Multiple toasts can be visible and must **stack predictably**.
- Timers must be **cleaned up** on unmount or when the toast is removed.
- We care about **accessibility**: ARIA roles (e.g. `status`, `alert`), screen reader announcements, and sensible focus behavior for interactive toasts.
Main concept
Global toast layer
One place in the app renders all toasts.
Key UX goal
Non-blocking feedback
Users keep working while seeing notifications.
Key technical concern
Timers & cleanup
No dangling intervals/timeouts when toasts disappear or routes change.
Key message to land
A toast system is more than just "show a div". In requirements, highlight that it must be global, stack correctly, clean up timers reliably, respect z-index layers, and stay accessible and readable on both desktop and mobile.
Architecture / High-level design
In this step, the interviewer wants to see if you can turn "toast.success()" into a clear architecture: where the global state lives, how to render toasts on top of everything, and how different parts of the app talk to the toast system without prop-drilling.
**How you should frame it:**
"At a high level, I’d have a global toast store, a ToastProvider at the top of the app that renders a ToastContainer via a portal, and a simple `toast` API that any code can call to add/remove toasts. The provider listens to the store and displays a stack of toasts in the chosen corner."
**What the interviewer listens for:**
- You separate responsibilities instead of building a giant "ToastGod" component.
- You clearly describe where **global state** lives.
- You mention a **portal** or equivalent technique to render above the main layout.
- You think about **z-index layering** and stacking.
- You keep the public API small and easy to use (`toast.success(...)`).
How you should act here
Describe 3–4 boxes: `toast` API → global store → ToastProvider → ToastContainer/ToastItem. Keep it simple and show that each part has a focused job. You want the interviewer to think: "I could plug this into my app easily."
Core building blocks you should mention
| Piece | Responsibility | What you say out loud |
|---|---|---|
| Toast API (`toast` helper) | Public functions like `toast.success()`, `toast.error()` that push toasts into global state. | "Anywhere in the app can call `toast.success(message)`; under the hood it just dispatches an 'add toast' action to a global store." |
| Global toast store / context | Keeps an array of active toasts and handles add/remove/clear actions. | "The toast store holds an array of toast objects and exposes methods to add and remove them. It’s the single source of truth for visible toasts." |
| ToastProvider | Top-level component that subscribes to the store and renders the ToastContainer via a portal. | "At the root of the app I’d wrap everything in a `ToastProvider` that listens to the toast state and renders the container in a fixed overlay." |
| ToastContainer | Positions and stacks toasts for a given placement (top-right, bottom-left, etc.). | "`ToastContainer` receives the list of toasts and a placement like 'top-right', and is responsible for stacking them and applying the right positioning/z-index." |
| ToastItem | Renders a single toast: icon, message, optional actions, close button, ARIA/focus behavior. | "Each `ToastItem` knows how to show the type (success/error), run its timer, and handle close clicks, including ARIA roles and focus if there’s a button." |
Things you should explicitly say
- There is **one global toast layer** mounted near the root of the app.
- Toasts are **stored in a global store/context**, not per-page state.
- The `toast` API is just a thin wrapper over that store (no UI logic inside helpers).
- The ToastProvider uses a **portal** (or similar) to render a fixed-position container above the main app.
- ToastContainer handles **placement** and **stacking order** (newest on top or bottom).
- ToastItem owns its own timer start/cleanup and notifies the store when it should be removed.
Architecture red flags you should avoid
- Spreading toast state across many unrelated components.
- Requiring every page to include its own toast container.
- Triggering DOM queries (`document.querySelector`) all over instead of a clean portal.
- Letting timers live outside React/state, making cleanup hard.
- Hard-coding placements and not leaving room for a simple config.
- No clear z-index strategy (toasts randomly appear behind headers or modals).
High-level flow you should walk through
1
1. App initialization
The root renders `<ToastProvider>` once, which mounts a ToastContainer in a fixed overlay using a portal.
→
2
2. Triggering a toast
Any component or service calls `toast.success(message)`. The helper dispatches an `addToast` action with a new toast object (id, type, message, options).
→
3
3. Rendering & stacking
The ToastProvider subscribes to the toast store, receives the updated list, and ToastContainer renders a stack of ToastItem components in the correct corner.
→
4
4. Auto-dismiss & manual close
Each ToastItem starts a timer on mount (unless disabled). When time is up or the user clicks close, it dispatches `removeToast(id)` to the store and cleans up its timer.
→
5
5. Unmount / navigation
If the ToastProvider unmounts (e.g. full app teardown), it clears remaining toasts and timers to avoid leaks.
State ownership
Global store
All toasts live in one place and are easy to observe.
Rendering strategy
Portal overlay
Toasts render above the main layout without breaking it.
Developer experience
`toast.*()` API
Callers don’t care about implementation details, only about a simple function.
Key message to land
A good toast architecture gives you one global place for state and rendering, plus a tiny, ergonomic API for the rest of the app. If you can explain that clearly with 3–4 boxes, you’re already giving a strong senior signal.
Data model
In this step, the interviewer wants to see if you can describe the data your toast system works with: how a single toast is represented, how the global toast list is stored, and which UI states (timers, variants, placement, accessibility) you treat as first-class data instead of hidden logic.
**How you should talk about data:**
"I’ll keep a simple model: a `Toast` type for each notification, a `ToastState` for the global list, and a `ToastConfig` for defaults like placement and duration. Each toast knows its id, type, message, lifetime, and placement."
**What the interviewer listens for:**
- You define a clear shape for a toast, not just "some object".
- You explicitly model things like type, placement and duration.
- You keep the global list and configuration small and predictable.
- You avoid mixing DOM or timer handles directly into the data model.
How you should act here
Name your main types out loud and keep them UI-focused: "Toast", "ToastState", maybe a "ToastConfig". Show that you’re modeling what the UI actually needs to render and manage toasts, not designing a backend schema.
Core entities you should define
| Entity | Fields (example) | What you say out loud |
|---|---|---|
| Toast | id, variant, message, title?, description?, placement?, autoDismiss, durationMs, createdAt, ariaRole?, actionLabel?, actionId? | "Each toast is a small object with an `id`, a `variant` like success/error, the message text, optional title/description, placement, auto-dismiss settings, and ARIA role. If it has an action like 'Undo', I also store `actionLabel` and an `actionId`." |
| ToastState | toasts[], defaultPlacement, defaultDurationMs, maxVisible | "`ToastState` is the global slice: an array of active toasts plus config like default placement, default duration and how many toasts can be visible at once." |
| ToastConfig (optional) | placement, durationMs, ariaRole, maxVisible | "When calling `toast.success(message, options?)`, I’d accept an options object that partially overrides defaults: placement, duration, ARIA role or max visible per call if needed." |
Things you should explicitly include in the model
- A **unique id** per toast so you can remove it reliably.
- A **variant** field for visual style and icon (success/error/etc.).
- A clear **message** and optional **title/description**.
- A **placement** field (or global default) to control where it appears.
- An **autoDismiss** flag and **durationMs** for timers.
- An **ARIA role** (`status`/`alert`) for screen reader behavior.
- Optional **action** fields for interactive toasts (label/id).
Things you should avoid putting into the data model
- Raw DOM nodes or refs inside the toast object.
- Actual timer/timeout handles in the model (keep them alongside the component logic or in a separate map).
- Random UI-only flags that can be derived (e.g. computing "isOld" from `createdAt` instead of storing another boolean).
- Mixing unrelated global UI state (like modals) into `ToastState`.
- Overcomplicating the schema for a simple notification use case.
How toast data typically changes over time
1
1. Add toast
User action triggers `toast.success("Saved")`. Under the hood, a new `Toast` object is created with a unique `id`, `variant = 'success'`, `message`, `createdAt`, and duration/placement from defaults or options. It is appended to `ToastState.toasts`.
→
2
2. Start lifetime
When the ToastItem mounts, it starts an auto-dismiss timer if `autoDismiss` is true. The visual component reads everything it needs from the `Toast` object.
→
3
3. Manual or auto dismiss
If the user clicks the close button or the timer fires, the system dispatches a `removeToast(id)` action that filters the `Toast` out of `ToastState.toasts`.
→
4
4. Optional action
If the toast has an action (e.g. "Undo"), clicking it can fire a callback identified by `actionId`, and then dismiss the toast by removing it from state.
Must-have entities
Toast + ToastState
If you clearly define these two, the rest of the design becomes much easier to explain.
Key idea
Model behavior as data
Variant, placement, auto-dismiss and ARIA role all live on the toast, not as scattered magic logic.
Good signal
Small, readable types
If another engineer can understand your model from a short interface snippet, you’re on the right track.
Key message to land
A solid toast data model treats each toast as a small, self-contained piece of state with clear fields for type, placement, lifetime, and accessibility. This keeps the rendering logic simple and makes the global store easy to reason about.
Interface definition (API)
In this step, the interviewer wants to see if you can design a clean, easy-to-use API for the toast system. You should show that you know how to separate the public surface (what other engineers call) from the internal implementation (store, provider, timers, etc.).
**How you should talk about APIs:**
"From the outside, I want a tiny API: `toast.success(message, options?)`, `toast.error(...)`, etc. Under the hood, these helpers just push a `Toast` object into a global store. The UI pieces (`ToastProvider`, `ToastContainer`) subscribe to that store and render toasts via a portal."
**What the interviewer listens for:**
- The public API is **simple and ergonomic**.
- You define clear contracts for **inputs and outputs**.
- You don’t leak implementation details (DOM, timers) into the public interface.
- You expose good extension points (options, callbacks) without overcomplicating it.
How you should act here
Speak in terms of function signatures and props, not only vague descriptions. You don’t need perfect TypeScript, but you should be able to say: "`toast.success(message, options?)` calls into a `ToastStore` with `{ message, variant: 'success', ...options }`."
Core interfaces you should define out loud
| Interface | Shape (example) | What you say out loud |
|---|---|---|
| ToastOptions | { title?, description?, placement?, autoDismiss?, durationMs?, ariaRole?, actionLabel?, onActionClick? } | "`ToastOptions` lets callers override defaults per toast: placement, duration, ARIA role, and optionally provide an action like 'Undo'." |
| ToastApi | success(message, options?), error(...), warning(...), info(...), dismiss(id), dismissAll() | "The public `toast` API has four helpers for variants plus `dismiss(id)` and `dismissAll()`. Each call returns the toast id so it can be manually dismissed if needed." |
| ToastProviderProps | { placement?, durationMs?, maxVisible?, children } | "`ToastProvider` wraps the app and configures global defaults like placement, default duration, and how many toasts can be visible at once." |
| Internal store methods | add(toastPartial), remove(id), clear(), subscribe(listener) | "Internally the store exposes methods like `add`, `remove`, `clear`, and `subscribe`. The `toast` API calls `add`, and the provider subscribes to render changes." |
| ToastContainer props (internal) | { toasts, placement } | "`ToastContainer` is given `toasts` plus a `placement` and is responsible for stacking them and positioning them correctly in the viewport." |
Things you should explicitly expose
- Simple helper functions: `toast.success`, `toast.error`, `toast.warning`, `toast.info`.
- An optional options object (`ToastOptions`) for per-toast overrides.
- A way to **dismiss** a specific toast (`dismiss(id)`) and **all** toasts (`dismissAll()`).
- Provider-level props for global defaults (placement, duration, maxVisible).
- A clear contract that `toast.*()` can be called from anywhere that has access to the API (not tied to one component).
Things you should avoid in the public API
- Exposing low-level details like `setTimeout` handles or DOM nodes.
- Requiring callers to pass full `Toast` objects instead of a simple message + options.
- Needing the caller to manually manage IDs or stacking order.
- Having separate, unrelated APIs for each variant with different shapes.
- Needing UI components to know about the internal store implementation.
Call flow you should describe
1
1. Caller triggers a toast
A component or service calls `toast.success('Profile saved', { durationMs: 5000 })`.
→
2
2. API builds a toast
The `toast` helper creates a `Toast` object with a new `id`, `variant = 'success'`, `message`, and merged options (placement, duration, etc.).
→
3
3. Store update
The helper calls into the toast store’s `add()` method. The store updates its `toasts` array and notifies subscribers.
→
4
4. Provider re-renders
The ToastProvider, subscribed to the store, receives the new `toasts` list and re-renders the ToastContainer via a portal.
→
5
5. Dismissal
When the toast auto-dismisses or the user closes it, the ToastItem calls `dismiss(id)` (which calls store `remove(id)`), and the provider re-renders without that toast.
API surface
Tiny & focused
Most apps only need `toast.*()` helpers and one provider.
Data flow
Down via props, up via actions
Callers send events in; the provider reads state and renders UI.
Strong signal
Clear contracts
You can explain what each function takes and returns in one sentence.
Key message to land
A good toast API feels effortless to use: one import, one provider, and a few tiny helpers. If another engineer can start using your system just by seeing `toast.success(message, options?)`, you’ve designed the interface well.
Optimizations and deep dive
In this final step, the interviewer wants to see if you can spot where a toast system might break in a real app and how you’d improve it. You should talk about performance (many toasts, frequent updates), timers and cleanup, accessibility details, z-index issues, and how you’d keep the UX sane on different devices.
**How you should frame optimizations:**
"I’d first ship a simple but correct version: global store, portal, timers, basic ARIA. Then I’d look at actual usage: what happens if many toasts fire? How does it feel on slower devices? Based on that, I’d tune max visible toasts, animations, stacking behavior, and accessibility details."
**What the interviewer listens for:**
- You start from a working baseline and then optimize based on real problems.
- You know common pain points: too many toasts, janky animations, timer leaks.
- You can go deeper on at least one topic (e.g. timers, accessibility, responsive layout).
- You think about both **UX polish** and **technical robustness**.
How you should act here
Pick a few realistic scenarios: "user triggers many toasts quickly", "mobile layout is cramped", "screen reader users", and walk through how you’d detect problems and improve them. Sound like someone who has actually seen toast systems misbehave in production.
Baseline hygiene you should mention
- Limit the **maximum number of visible toasts** (e.g. 3–5) and remove or queue older ones.
- Ensure timers are always **cleared on unmount** or when toasts are removed.
- Pause auto-dismiss while the user **hovers** or focuses a toast (so they can read or click).
- Debounce or collapse identical messages (e.g. repeated "Network error" toasts).
- Use **CSS transitions** for enter/exit animations instead of heavy JS-based animation loops.
Accessibility and UX deep-dive points
- Use appropriate **ARIA roles**: `status` for non-urgent info, `alert` for critical errors.
- Announce only the **important text** to screen readers, not decorative content.
- For interactive toasts with buttons, ensure **focus is visible** and keyboard navigation works.
- Respect **reduced motion** preferences by disabling or simplifying animations.
- On mobile, consider **full-width** toasts at top/bottom with larger touch targets.
Deep-dive topics you can offer
| Topic | Angle | How you talk about it |
|---|---|---|
| Z-index and layering | Toasts vs modals vs dropdowns | "I’d pick a dedicated z-index range for toasts so they appear above normal content but don’t unexpectedly cover modal dialogs unless we explicitly want that. This avoids random layering bugs across the app." |
| Handling toast storms | Too many notifications | "If a feature fires a lot of toasts in a short time, I’d cap the visible count and either drop the oldest ones or group them (e.g. '5 new notifications') instead of flooding the screen." |
| Route changes | Navigation & cleanup | "On navigation, I’d decide whether to keep or clear toasts. For most flows I’d clear them to avoid old messages leaking into a new context, and in all cases I’d ensure timers are cleaned up." |
| Performance on low-end devices | Avoid jank | "I’d keep toast components lightweight, avoid heavy shadows/filters, and reuse layout classes. If profiling shows jank, I’d simplify the toast UI and animations first, before any complex tricks." |
| Action toasts (Undo) | State consistency | "For actions like 'Undo', I’d make sure the toast’s action callback works well with the rest of the app’s state, and that the toast doesn’t disappear before the user has a chance to act if they’re interacting with it." |
Optimization path you should describe
1
1. Ship a clean baseline
Global store, portal-based rendering, maxVisible, basic ARIA roles, auto-dismiss with proper cleanup, simple stacking.
→
2
2. Observe usage
See how many toasts are typically active, which variants are used most, and whether users trigger bursts of notifications.
→
3
3. Fix obvious pain points
Cap visible toasts, add hover/focus pause, tweak durations (shorter for success, longer for errors), and reduce noisy duplicates.
→
4
4. Polish accessibility & responsiveness
Verify keyboard navigation, screen reader announcements, mobile layout, and behavior under reduced-motion settings.
→
5
5. Hardening & edge cases
Test route changes, app teardown, error boundaries, and ensure there are no timer leaks or orphaned toasts in weird flows.
Biggest UX risk
Toast overload
Too many notifications quickly become background noise.
Biggest tech risk
Timer leaks
Forgetting to clear timeouts on unmount can cause bugs and memory issues.
Strong senior signal
Measure → then tune
You talk about real usage, profiling and trade-offs instead of random micro-optimizations.
Key message to land
A great toast system isn’t just "it shows notifications". It behaves well when spammed, respects accessibility and motion preferences, cleans up timers reliably, and stays readable and unobtrusive on both desktop and mobile.
Use guided tracks for structured prep, then drill company-specific question sets when you need targeted practice.