Interview questions actually asked at product companies and startups for React developer roles. Covers hooks, state, performance, lifecycle, and modern React patterns. By Deen Bandhu.
React.createElement() calls. Rules: one root element (or Fragment <></>), use className not class, htmlFor not for, self-close empty tags, expressions in { }.setState/setter, triggers re-render when changed. Rule: a component should never modify its own props. Use state for dynamic data, props for configuration.value and onChange. React is the "single source of truth". Uncontrolled: input manages its own value in the DOM. Access via useRef. Controlled is preferred — easier to validate, test, and synchronize.Math.random().this binding issues. Rules: only call at the top level (not inside loops/conditionals), only call from React functions.const [count, setCount] = useState(0). Returns current value and a setter. Calling setter with a new value triggers re-render. Key gotchas: state updates are asynchronous (don't read state immediately after setting it); for state based on previous value use functional update: setCount(prev => prev + 1) — avoids stale closure bugs.useEffect(callback, deps). Runs after render. Dependency array controls when: no array → runs after every render; empty array [] → runs once after mount (componentDidMount equivalent); with deps → runs when any dep changes. Return a cleanup function for subscriptions, timers, event listeners.{ current: value } that persists across renders. Two uses: 1) DOM access — attach to element via ref prop (inputRef.current.focus()). 2) Store mutable value without triggering re-render — e.g. store previous value, timer ID, interval ID. Changing .current does NOT cause a re-render.React.memo) to prevent unnecessary re-renders. Don't overuse — they add overhead; profile first.const value = useContext(MyContext). Context re-renders all consumers when its value changes — watch out for performance. Split contexts by update frequency (separate AuthContext and ThemeContext). For complex state, combine with useReducer.const [state, dispatch] = useReducer(reducer, initialState). Prefer useReducer when: state is an object with multiple sub-values, next state depends on previous state in complex ways, or you have many related state updates. Similar to Redux pattern — reducer is a pure function (state, action) => newState.use that encapsulate and reuse stateful logic. Examples: useFetch(url) for data fetching, useLocalStorage(key), useDebounce(value, delay). Custom hooks let you extract repetitive logic out of components — makes components cleaner and logic testable. Custom hooks can call other hooks.useCallback for function props to be stable. Don't wrap every component — profile first, memoize only what's needed.React.lazy() + Suspense: const Dashboard = React.lazy(() => import('./Dashboard')). Wrap with <Suspense fallback={<Spinner/>}>. Reduces initial bundle size — critical for performance. Combine with React Router to lazy-load routes.React.memo on expensive child components. 3) useCallback to stabilize function props. 4) useMemo for expensive computations. 5) Split Context so consumers only re-render when relevant data changes. 6) Virtualize long lists with react-window. 7) Move state down — don't lift state higher than necessary.componentDidMount → useEffect(() => {}, []). componentDidUpdate → useEffect(() => {}, [deps]). componentWillUnmount → cleanup function returned from useEffect: useEffect(() => { return () => cleanup(); }, []). shouldComponentUpdate → React.memo.clearInterval/clearTimeout), removing event listeners, cancelling subscriptions (WebSocket, API with AbortController). Without cleanup: memory leaks and "setState on unmounted component" errors.useRef to store the latest value, or use the functional update form of setState.children prop or named slots. Example: <Card header={<Title/>} footer={<Actions/>}>content</Card> instead of <Card title="..." actions={...}>. Avoids prop drilling and creates open/closed components.withAuth(Component) wraps Component and redirects to login if not authenticated. HOCs were the pre-hooks pattern for logic reuse. Today, custom hooks are preferred — simpler, no wrapper hell, easier to compose.componentDidCatch and getDerivedStateFromError. Note: Error Boundaries don't catch errors in event handlers or async code (use regular try-catch there). React 18: use the react-error-boundary library for hooks-friendly version.setState calls into one render — even in async functions, event handlers, and promises. React 17 only batched inside React event handlers. Means fewer re-renders with no code change. To opt out of batching for a specific update, use ReactDOM.flushSync().const [isPending, startTransition] = useTransition(). Use for slow, non-urgent UI updates (filtering a large list). The urgent update (typing) stays responsive while the transition (filtered results) renders in background.<Suspense fallback={<Loading/>}><LazyComponent/></Suspense>. React 18 extends Suspense to data fetching with frameworks like Next.js and React Query. Enables streaming SSR with renderToPipeableStream.AlgoVentra's Full Stack course covers React and Spring Boot — you build a complete full stack app with JWT auth, REST APIs, and a deployed React frontend.