React Curriculum

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.

  1. 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}`);
    Show the output
    object symbol true div
    Why it prints this, traced line by line
  2. 02 · 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());
    Show the output
    0 0 1 false
    Why it prints this, traced line by line
  3. 03 · 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());
    Show the output
    cursor:end cursor:preserved
    Why it prints this, traced line by line
  4. 04 · 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());
    Show the output
    0 false C-B-A
    Why it prints this, traced line by line
  5. 05 · 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());
    Show the output
    inner-portal outer-container true
    Why it prints this, traced line by line
  6. 06 · 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());
    Show the output
    1 1 2
    Why it prints this, traced line by line
  7. 07 · 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());
    Show the output
    1 3 0 3
    Why it prints this, traced line by line
  8. 08 · 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());
    Show the output
    double-mount clean mount 1
    Why it prints this, traced line by line
  9. 09 · 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());
    Show the output
    0 5 0 true
    Why it prints this, traced line by line
  10. 10 · 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());
    Show the output
    parent-render child-memo true
    Why it prints this, traced line by line
  11. 11 · 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());
    Show the output
    remount keep-state recycle
    Why it prints this, traced line by line
  12. 12 · 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());
    Show the output
    target-react root-react doc-native
    Why it prints this, traced line by line
  13. 13 · 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());
    Show the output
    shallow-fail custom-pass 1
    Why it prints this, traced line by line
  14. 14 · 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());
    Show the output
    urgent:typed deferred:stale deferred:caught-up
    Why it prints this, traced line by line
  15. 15 · 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());
    Show the output
    1 1 3 3
    Why it prints this, traced line by line
  16. 16 · 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());
    Show the output
    child-effect parent-effect child-cleanup
    Why it prints this, traced line by line
  17. 17 · 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());
    Show the output
    memo-bypassed memo-retained
    Why it prints this, traced line by line
  18. 18 · 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());
    Show the output
    state-reset state-preserved
    Why it prints this, traced line by line
  19. 19 · 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());
    Show the output
    insertion layout paint passive
    Why it prints this, traced line by line
  20. 20 · 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());
    Show the output
    hydration-mismatch server-match
    Why it prints this, traced line by line