Prompt and scope
You maintain a multi-column dashboard. A card's size is determined by the grid, dragging, the sidebar, and font loading, so it cannot be treated as a function of viewport size alone. Below 320px it shows a compact title and one-column metrics; above the threshold it shows the full layout. Collapsing the sidebar without changing the window must take effect immediately.
This question tests element-level measurement, browser layout timing, React lifecycle, and performance boundaries. Public front-end interview material commonly covers DOM, CSS, browser events, and performance; the platform documentation gives precise ResizeObserver notification and loop behavior for deeper follow-ups.
What the interviewer is testing
- Explain that
window.resizedescribes viewport changes, not grid reflows, font swaps, or parent-size changes. - Choose
content-boxorborder-boxand define what the threshold means. - Write measurements to state or CSS variables without synchronously resizing the observed element.
- Handle target replacement, unmounting, hidden nodes, older browsers, and many cards.
- Prove smoothness with user metrics, long tasks, and layout traces rather than callback counts alone.
Questions to clarify first
- Is the threshold about content width, border-box width, or a style-only decision that CSS container queries can express?
- Should size changes trigger data work, or only change presentation?
- Must the UI follow every frame, or is one coalesced update per frame sufficient?
- What is the browser support floor, and can server rendering touch
window? - Are there dozens or thousands of cards, and can only visible cards be observed?
A 30-second answer
“I would first establish that this is an element-size problem, not a viewport-size problem. For style-only breakpoints I would prefer CSS container queries. If JavaScript needs the measurement, I would attach a ResizeObserver on the client, read a defined box, deduplicate the threshold state, and write a CSS variable or discrete state. The callback should not synchronously change the observed size; nonessential writes can be scheduled for the next frame, and cleanup must disconnect the observer. For many cards I would bound the observed set and measure INP, long tasks, and layout cost. Older browsers get an explicit fixed-layout, container-query, or throttled viewport fallback.”
Deep-dive answer
Step 1: Check whether CSS is enough
If the requirement is only “show compact styles below 320px,” a container query is usually simpler and keeps measurement out of JavaScript. ResizeObserver is justified when size drives chart sampling, virtualization, a third-party renderer, or observable business logic.
Step 2: Define the size contract
ResizeObserverEntry exposes content and border box measurements. Define which box owns the threshold, then standardize units and rounding. A floating-point sample is not automatically a business event; deduplicate a 319.9-to-320.1 transition as a breakpoint state when that is the product contract.
Step 3: Establish the observer lifecycle
Create the observer after the client component mounts, observe the card node, and call unobserve or disconnect when the node changes or the component unmounts. In React, let a ref own the target and an Effect own observer creation and cleanup so ordinary renders do not recreate it.
const ref = useRef<HTMLDivElement>(null)
const [compact, setCompact] = useState(false)
useEffect(() => {
const node = ref.current
if (!node || !('ResizeObserver' in window)) return
const observer = new ResizeObserver(([entry]) => {
const width = entry.contentRect.width
const next = width < 320
setCompact((current) => (current === next ? current : next))
})
observer.observe(node)
return () => observer.disconnect()
}, [])Step 4: Prevent a callback feedback loop
If the callback changes the observed element's width or height, that change can schedule another notification and eventually produce ResizeObserver loop completed with undelivered notifications. Keep the callback focused on the size contract, or schedule visual writes with requestAnimationFrame and make them idempotent.
Step 5: Separate measurement from rendering
Write measurements to a CSS custom property when CSS can own the layout, or keep only a discrete state such as compact. Do not put every pixel change into React state during a drag. If a continuous value is required, coalesce notifications per animation frame and record dropped frames and long tasks.
Step 6: Handle many cards and hidden nodes
For thousands of cards, observe only visible nodes or let a layout layer distribute measurements. display: none, collapsed panels, and virtualization change observable sizes; after a node becomes visible, verify that the first notification restores the correct state. An observer is not a polling loop.
Step 7: Define fallback and server boundaries
Server rendering cannot access window. Detect support inside a client Effect. Without ResizeObserver, use a fixed layout, CSS media/container rules, or a throttled viewport listener, and document the element-level behavior that the fallback cannot provide.
Step 8: Verify with evidence
Test sidebar collapse, dragging, font loading, grid reflow, rotation, and browser zoom. Record callbacks, layout, paint, and long tasks in Chrome Performance; use INP or input delay to check drag responsiveness. Add a test for the console loop warning and verify that an unmounted card no longer receives updates.
Trade-offs and boundaries
Container queries versus ResizeObserver
Container queries fit style-only breakpoints and remain declarative. ResizeObserver fits JavaScript calculations and third-party rendering but requires lifecycle and performance controls. The boundary is whether the measurement must leave the style layer.
content-box versus border-box
Use content-box for content-layout thresholds and border-box for an outer card contract that includes padding and borders. A wrong choice shifts the breakpoint; put the choice in the contract and tests.
Continuous versus discrete values
Continuous width can drive a chart but updates frequently. A discrete breakpoint is stable and easier to test. Start with the discrete contract and add continuous updates only with a measured budget and clear visual benefit.
Failure drills and evolution
Failure: listen only to window.resize
Sidebar collapse and grid reflow do not change the viewport, so the card stays in the wrong layout. Observe the card or its container and test non-viewport changes.
Failure: mutate the observed size in the callback
The mutation triggers another callback and can create a loop and extra layout. Write an independent CSS variable, use a discrete state, or schedule an idempotent next-frame update.
Failure: setState for every pixel
Dragging creates high-frequency React renders and worsens input response. Deduplicate thresholds, coalesce with rAF, observe visible cards only, and confirm the cost in Performance.
Common mistakes and follow-ups
Mistake: treating ResizeObserver as a stronger resize event
It observes an element's box and delivers notifications according to layout timing; it is not a simple replacement for a viewport event.
Follow-up: how do you verify React Strict Mode?
Ensure development setup and cleanup remain paired and observer counts do not accumulate. Do not mistake the extra development check for production duplicate subscriptions.
Follow-up: can the callback call getBoundingClientRect?
It can, but the extra measurement may add layout cost. Prefer the entry's box values and prove any additional read with a performance trace.
Follow-up: how do you test a size loop?
Make the callback change a style that affects its own size, observe warnings, notification count, and final size, then assert the fixed version stabilizes without continuous notifications.
Follow-up: when is JavaScript unnecessary?
When the requirement is only a container breakpoint style change, prefer CSS container queries. Keep JavaScript for data, measurement, or third-party rendering that truly needs it.
Follow-up: how do you prove the optimization worked?
Compare INP, long-task count, layout time, React commits, and memory under the same drag script. Callback count alone is not a user-experience metric.