React Output Based Questions, with Answers
20 "what does this render or log?" questions of the kind React interviewers ask about state batching, effect order, keys, memo and refs. Read the code, write down the output, then open the answer.
They run from Easy to Hard. Each one links to a lesson that traces the code line by line, so a wrong guess shows you exactly where your model of React went off. More questions by level: for freshers and for experienced developers.
01 · React Fundamentals for Frontend Interviews · Easy
JSX Compilation & Modern JSX Runtime
What is logged to the console when the following simulation inspecting a modern JSX transpiled element executes?
// Simulating the modern JSX runtime output const REACT_ELEMENT_TYPE = Symbol.for('react.element'); function jsx(type: string, props: Record<string, any>) { return { $$typeof: REACT_ELEMENT_TYPE, type, props, key: null, ref: null, }; } // Equivalent to: <div className="card">Hello</div> const element = jsx('div', { className: 'card', children: 'Hello' }); const typeOfElement = typeof element; const typeOfSymbol = typeof element.$$typeof; const isReactElement = element.$$typeof === Symbol.for('react.element'); const nodeType = element.type; console.log(`${typeOfElement} ${typeOfSymbol} ${isReactElement} ${nodeType}`);Why it prints this, traced line by lineShow the output
object symbol true div02 · State Mechanics, Batching, and Lifecycle Transitions · Easy
State Mental Model & Memory Snapshots
What is logged to the console when the following component mutates an object state in-place, then creates a new object copy with structural sharing?
// Simulation of React useState snapshot and reference equality function simulateState() { let committedState = { count: 0, meta: { version: 1 } }; // Render 1 captures snapshot const snapshotCount = committedState.count; // In-place mutation (Anti-pattern!) committedState.count = 5; // React checks Object.is(oldState, newState) -> Same reference, NO render triggered! const didRerenderOnMutation = false; // Proper immutable update with structural sharing const nextState = { ...committedState, count: 1, }; const isSameRef = Object.is(committedState, nextState); return `${snapshotCount} ${didRerenderOnMutation ? 1 : 0} ${nextState.count} ${isSameRef}`; } console.log(simulateState());Why it prints this, traced line by lineShow the output
0 0 1 false03 · Form Architecture, Modern FormData, and Validation · Easy
Controlled vs Uncontrolled Inputs & Cursors
What is logged to the console when contrasting an asynchronous reformat that causes an input cursor jump against synchronous cursor preservation?
// Simulating controlled input cursor behavior function simulateInputCursor() { const logs: string[] = []; // When value is updated asynchronously in controlled inputs, // the browser resets selectionStart/selectionEnd to the end of the input string! const asyncReformatCursor = 'cursor:end'; logs.push(asyncReformatCursor); // When selectionRange is explicitly preserved using refs: const preservedCursor = 'cursor:preserved'; logs.push(preservedCursor); return logs.join(' '); } console.log(simulateInputCursor());Why it prints this, traced line by lineShow the output
cursor:end cursor:preserved04 · React Fundamentals for Frontend Interviews · Medium
Conditional Rendering & List Keys
What is rendered to the DOM when the following component evaluates a zero count condition and reverses a list of stateful items indexed by array index?
// Simulation of React rendering semantics function simulateRender() { const count = 0; // Bug trap: 0 && <span /> evaluates to 0, which React renders as text! const conditionalOutput = count && 'Items Available'; const hasItems = count > 0; const safeOutput = hasItems && 'Items Available'; // List keyed with stable IDs: 'A', 'B', 'C' -> reversed to 'C', 'B', 'A' const items = ['A', 'B', 'C']; const reversed = [...items].reverse(); const listKeys = reversed.join('-'); return `${conditionalOutput} ${Boolean(safeOutput)} ${listKeys}`; } console.log(simulateRender());Why it prints this, traced line by lineShow the output
0 false C-B-A05 · React Fundamentals for Frontend Interviews · Medium
Fragments, Portals & Event Bubbling
What is logged to the console when a click event triggers on a child element rendered via a React Portal into document.body, captured by a parent React container?
// Simulation of React Portal event propagation function simulatePortalEvent() { const eventsCaptured: string[] = []; // Child component rendered via createPortal into #portal-root (separate DOM tree) function handleChildClick() { eventsCaptured.push('inner-portal'); } // Parent component in main React tree function handleParentClick() { eventsCaptured.push('outer-container'); } // Trigger click inside portal handleChildClick(); // React SyntheticEvent bubbles through the React Virtual DOM hierarchy, // NOT the physical DOM hierarchy! handleParentClick(); const isBubbledAcrossDOM = eventsCaptured.length === 2; return `${eventsCaptured.join(' ')} ${isBubbledAcrossDOM}`; } console.log(simulatePortalEvent());Why it prints this, traced line by lineShow the output
inner-portal outer-container true06 · State Mechanics, Batching, and Lifecycle Transitions · Medium
State Updates & React 18 Automatic Batching
What is logged to the console when simulating state batching inside an asynchronous Promise in React 17 vs React 18 vs flushSync?
// Simulating render counts for 2 consecutive state updates inside an async callback function simulateBatching() { // React 17: Async callbacks (setTimeout, Promise) did NOT batch -> 2 renders const react17AsyncRenders = 2; // React 18: Automatic Batching groups async state updates -> 1 render const react18AsyncRenders = 1; // React 18 flushSync: Forces immediate synchronous DOM flush -> 1 render per call -> 2 renders const flushSyncRenders = 2; return `${react18AsyncRenders} ${react18AsyncRenders} ${flushSyncRenders}`; } console.log(simulateBatching());Why it prints this, traced line by lineShow the output
1 1 207 · State Mechanics, Batching, and Lifecycle Transitions · Medium
Functional Updaters & Stale State Resolution
What is logged to the console when executing two direct updates, followed by two functional updates, followed by an invocation of an asynchronous stale closure callback and a ref?
function createReactRuntime() { let committedState = 0; function render1() { const count = committedState; const staleCallback = () => count; const updateDirect = () => count + 1; const updateFunctional = (prev: number) => prev + 1; return { staleCallback, updateDirect, updateFunctional }; } const r1 = render1(); // Direct updates both evaluate captured count = 0: 0 + 1 = 1 committedState = r1.updateDirect(); committedState = r1.updateDirect(); const countAfterDirect = committedState; // Functional updates chain: 1 -> 2 -> 3 committedState = r1.updateFunctional(committedState); committedState = r1.updateFunctional(committedState); const countAfterFunctional = committedState; // Closure created in render1 permanently closed over count = 0 const staleRead = r1.staleCallback(); // Mutable ref bypasses closure capture const ref = { current: 0 }; ref.current = 3; const refRead = ref.current; return `${countAfterDirect} ${countAfterFunctional} ${staleRead} ${refRead}`; } console.log(createReactRuntime());Why it prints this, traced line by lineShow the output
1 3 0 308 · Core React Hooks: State, Synchronization, and Refs · Medium
useEffect Anti-Patterns & Re-render Traps
What is logged to the console when simulating a component mounting under React 18 StrictMode with an effect that tracks mount count and cleans up properly?
// Simulating React 18 StrictMode effect execution order function simulateStrictModeEffect() { const logs: string[] = []; // Mount 1 logs.push('double-mount'); // StrictMode immediately runs cleanup in dev! logs.push('clean'); // Mount 2 (Re-mount) logs.push('mount'); // Effect has run twice, but active subscription count is 1 const activeSubscriptions = 1; return `${logs.join(' ')} ${activeSubscriptions}`; } console.log(simulateStrictModeEffect());Why it prints this, traced line by lineShow the output
double-mount clean mount 109 · Core React Hooks: State, Synchronization, and Refs · Medium
useRef: DOM Access & Mutable Containers
What is logged to the console when mutating ref.current across renders compared to updating component state?
// Simulating useRef vs useState mechanics function simulateRefBehavior() { let renderCount = 0; const countState = 0; const countRef = { current: 0 }; // Mutating ref does NOT trigger a re-render countRef.current = 5; const rendersAfterRefMutation = renderCount; // Still 0 // State update schedules re-render (renderCount becomes 1) const isSameRefObjectAcrossRenders = true; return `${rendersAfterRefMutation} ${countRef.current} ${countState} ${isSameRefObjectAcrossRenders}`; } console.log(simulateRefBehavior());Why it prints this, traced line by lineShow the output
0 5 0 true10 · Advanced Hooks, Referential Equality, and Stores · Medium
useMemo & useCallback: Referential Stability
What is logged to the console when a parent component re-renders while passing a memoized callback and a non-memoized object to a React.memo child?
// Simulating referential stability across render cycles function simulateMemo() { const logs: string[] = []; // Render 1 logs.push('parent-render'); // Callback wrapped in useCallback preserves reference identity const fn1 = () => 'action'; const fn2 = fn1; // useCallback with stable deps returns same pointer const isCallbackStable = Object.is(fn1, fn2); if (isCallbackStable) { logs.push('child-memo'); } return `${logs.join(' ')} ${isCallbackStable}`; } console.log(simulateMemo());Why it prints this, traced line by lineShow the output
parent-render child-memo true11 · React Internals: Fiber Architecture and Reconciliation · Medium
The Reconciliation Diffing Algorithm & Heuristics
What is logged to the console when comparing state preservation between different element types, same element types with different keys, and identical element types?
// Simulating React tree comparison heuristics function simulateReconciliation() { const results: string[] = []; // 1. Different element types: <div> -> <section> destroys & remounts const changeType = 'remount'; results.push(changeType); // 2. Same element type, same position, no key change: preserves state! const sameTypeSameKey = 'keep-state'; results.push(sameTypeSameKey); // 3. Same element type, attribute changed (className): recycles DOM node const attrChange = 'recycle'; results.push(attrChange); return results.join(' '); } console.log(simulateReconciliation());Why it prints this, traced line by lineShow the output
remount keep-state recycle12 · React Internals: Fiber Architecture and Reconciliation · Medium
SyntheticEvent System & Event Delegation
What is logged to the console when tracing the execution order of a button click with a React handler, a React root container handler, and a native document listener in React 17+?
// Simulating event bubbling in React 17+ function simulateEventBubbling() { const logs: string[] = []; // In React 17+, all React events are delegated to #root! // When a button inside #root is clicked: // 1. Native click bubbles up to #root container // 2. React dispatches SyntheticEvents down & up component tree logs.push('target-react'); logs.push('root-react'); // 3. Native event bubbles past #root to document! logs.push('doc-native'); return logs.join(' '); } console.log(simulateEventBubbling());Why it prints this, traced line by lineShow the output
target-react root-react doc-native13 · Performance Engineering, Profiling, and Virtualization · Medium
React.memo & Custom Comparator Functions
What is logged to the console when comparing a standard React.memo component with inline props against a React.memo component with a custom arePropsEqual comparator?
// Simulating React.memo comparison behavior function simulateReactMemo() { const logs: string[] = []; // 1. Standard shallow comparison with new inline object reference: const obj1 = { id: 1 }; const obj2 = { id: 1 }; const isShallowEqual = Object.is(obj1, obj2); // false! if (!isShallowEqual) { logs.push('shallow-fail'); } // 2. Custom arePropsEqual comparator checks data value: function arePropsEqual(prev: { user: { id: number } }, next: { user: { id: number } }) { return prev.user.id === next.user.id; // true -> skips render! } const isCustomEqual = arePropsEqual({ user: obj1 }, { user: obj2 }); if (isCustomEqual) { logs.push('custom-pass'); } const renderCount = 1; // Only 1 render because custom comparator skipped update! return `${logs.join(' ')} ${renderCount}`; } console.log(simulateReactMemo());Why it prints this, traced line by lineShow the output
shallow-fail custom-pass 114 · Concurrent Rendering, Priority Lanes, and React 19 · Medium
useDeferredValue: Deferring Computations
What is logged to the console during an urgent state update when inspecting the immediate value versus the deferred value returned by useDeferredValue?
// Simulating useDeferredValue execution lifecycle function simulateDeferredValue() { const logs: string[] = []; // Pass 1: Urgent render triggered by typing 'apple' const immediateValue = 'apple'; let deferredValue = 'appl'; // Lags behind with previous value! logs.push(`urgent:${immediateValue === 'apple' ? 'typed' : 'pending'}`); logs.push(`deferred:${deferredValue !== immediateValue ? 'stale' : 'sync'}`); // Pass 2: In background, React completes deferred update deferredValue = 'apple'; logs.push(`deferred:${deferredValue === immediateValue ? 'caught-up' : 'stale'}`); return logs.join(' '); } console.log(simulateDeferredValue());Why it prints this, traced line by lineShow the output
urgent:typed deferred:stale deferred:caught-up15 · Tricky React Output Prediction & Execution Ordering · Medium
Output Traps: State Batching, Snapshots & Stale Closures
Predict the exact console output when the following simulation of state updates runs: first with three direct replacements, then with three functional updates.
// Simulating React state batching and updater queue function testStateBatching() { const outputs: number[] = []; // Pass 1: Direct replacements with initial count = 0 let countSnapshot = 0; let pendingUpdate = countSnapshot; // setCount(count + 1); setCount(count + 1); setCount(count + 1); pendingUpdate = countSnapshot + 1; // 1 pendingUpdate = countSnapshot + 1; // 1 pendingUpdate = countSnapshot + 1; // 1 outputs.push(pendingUpdate); // 1 outputs.push(countSnapshot + 1); // 1 // Pass 2: Functional updaters with initial count = 0 // setCount(c => c + 1); setCount(c => c + 1); setCount(c => c + 1); let queuedState = 0; const queue = [(c: number) => c + 1, (c: number) => c + 1, (c: number) => c + 1]; for (const updater of queue) { queuedState = updater(queuedState); } outputs.push(queuedState); // 3 outputs.push(queuedState); // 3 return outputs.join(' '); } console.log(testStateBatching());Why it prints this, traced line by lineShow the output
1 1 3 316 · Tricky React Output Prediction & Execution Ordering · Medium
Output Traps: Effect Order, Cleanups & StrictMode
Predict the exact console output when a Parent component containing a Child component mounts, and then the Child unmounts.
// Simulating React component tree effect order function simulateEffectLifecycle() { const log: string[] = []; // Mount Phase: // Render phase goes top-down: Parent renders -> Child renders // Commit passive effects phase goes bottom-up (post-order): // 1. Child useEffect runs first! log.push('child-effect'); // 2. Parent useEffect runs second! log.push('parent-effect'); // Unmount / Dependency Change Phase: // 3. Child cleanup runs when unmounted or before re-running! log.push('child-cleanup'); return log.join(' '); } console.log(simulateEffectLifecycle());Why it prints this, traced line by lineShow the output
child-effect parent-effect child-cleanup17 · Tricky React Output Prediction & Execution Ordering · Medium
Output Traps: React.memo & Referential Identity Pitfalls
Predict the exact output when testing a React.memo component receiving an unstable inline object versus a referentially stable memoized object.
// Simulating React.memo shallow comparison behavior function testMemoBehavior() { const events: string[] = []; // Pass 1: Parent renders and passes inline object { theme: 'dark' } const prevProps1 = { theme: 'dark' }; const nextProps1 = { theme: 'dark' }; // New object reference in memory! const isShallowEqual1 = Object.is(prevProps1, nextProps1); // false! if (!isShallowEqual1) { events.push('memo-bypassed'); // Re-renders despite identical contents! } else { events.push('memo-retained'); } // Pass 2: Parent passes referentially stable constant or useMemo result const stableObject = { theme: 'dark' }; const prevProps2 = stableObject; const nextProps2 = stableObject; // Same pointer reference! const isShallowEqual2 = Object.is(prevProps2, nextProps2); // true! if (!isShallowEqual2) { events.push('memo-bypassed'); } else { events.push('memo-retained'); // Skips re-render! } return events.join(' '); } console.log(testMemoBehavior());Why it prints this, traced line by lineShow the output
memo-bypassed memo-retained18 · Tricky React Output Prediction & Execution Ordering · Medium
Output Traps: Tree Position & Component Key Identity
Predict the exact console output when comparing state preservation between a component whose key prop changes versus one whose key remains identical across renders.
// Simulating React key identity and state preservation function testKeyReconciliation() { const results: string[] = []; // Case 1: Key changes from 'user-1' to 'user-2' // React treats different keys as completely different elements! // Destroys old Fiber instance, resets state to initial default. const keyChanged = 'state-reset'; results.push(keyChanged); // Case 2: Same element type, same key 'user-1', same position in tree // React reconciles Fiber node, preserves existing local state! const keyPreserved = 'state-preserved'; results.push(keyPreserved); return results.join(' '); } console.log(testKeyReconciliation());Why it prints this, traced line by lineShow the output
state-reset state-preserved19 · Advanced Hooks, Referential Equality, and Stores · Hard
useLayoutEffect vs useEffect vs useInsertionEffect
What is logged to the console when tracing the execution sequence of useInsertionEffect, useLayoutEffect, browser paint, and useEffect for a mounted component?
// Simulating React commit phase hook execution timeline function simulateHookExecutionPhases() { const timeline: string[] = []; // 1. Before DOM mutations: useInsertionEffect (CSS-in-JS injection) timeline.push('insertion'); // DOM mutations occur... // 2. Synchronous post-mutation before browser paint: useLayoutEffect timeline.push('layout'); // 3. Browser performs layout & paint to the screen timeline.push('paint'); // 4. Asynchronous passive effect after browser paint: useEffect timeline.push('passive'); return timeline.join(' '); } console.log(simulateHookExecutionPhases());Why it prints this, traced line by lineShow the output
insertion layout paint passive20 · Server-Side Rendering, Hydration, and Server Components · Hard
Hydration Mismatch Errors: Diagnosis, Prevention & Fixes
What is logged when comparing an uncoordinated timestamp render that triggers a hydration mismatch versus a synchronized two-pass hydration pattern?
// Simulating Hydration Mismatch Resolution function simulateHydrationBehavior() { const events: string[] = []; // Scenario 1: Direct client/server divergence // Server renders: "Server-100", Client renders: "Client-200" // React detects mismatch and logs error const mismatchResult = 'hydration-mismatch'; events.push(mismatchResult); // Scenario 2: Synchronized Two-Pass Render // Both server and initial client render match identically! // Client updates only AFTER mounting in useEffect const synchronizedResult = 'server-match'; events.push(synchronizedResult); return events.join(' '); } console.log(simulateHydrationBehavior());Why it prints this, traced line by lineShow the output
hydration-mismatch server-match
