FE
Frontend Interview Preparation
What you need for a Frontend (FE) tech interview:
Core Skills
- Strong understanding of HTML, CSS, and JavaScript (ES6+)
- Deep knowledge of at least one modern frontend framework (React, Vue, Angular)
- Familiarity with state management (Redux, Context API, Vuex, etc.)
- Experience with REST APIs and/or GraphQL
- Responsive and accessible web design principles
- Component-based architecture and modular code
Coding & Problem Solving
- Ability to solve algorithm and data structure problems in JavaScript
- Comfort with live coding and whiteboard exercises
- Debugging and performance optimization skills
- Experience with build tools (Webpack, Vite, etc.) and package managers (npm,
yarn)
- Version control with Git
- Testing (unit, integration, E2E with Jest, Testing Library, Cypress, etc.)
Soft Skills
- Clear communication and ability to explain technical concepts
- Code review and collaboration experience
Interview Preparation
- Practice common FE interview questions (see the Q&A section)
- Build and review small projects or coding challenges
- Be ready to discuss trade-offs, best practices, and recent trends in frontend
development
1 - React Interview
Most Probable React Interview Questions (2025)
Most Probable React Interview Questions (2025) — with thorough answers
Curated and ordered by likelihood based on common interview patterns for React
engineers in 2024–2025. Answers are practical, detailed, and include pitfalls,
examples, and “when to use” guidance.
1) What is the difference between functional and class components?
- Class components use ES6 classes, have lifecycle methods
(componentDidMount, etc.), and use
this.state and this.setState. - Functional components are plain functions. With hooks (since React 16.8),
they can use state (
useState), effects (useEffect), and more. They are now
the standard for new React code.
Example:
// Class component
class MyComponent extends React.Component {
state = { count: 0 };
render() {
return (
<button onClick={() => this.setState({ count: this.state.count + 1 })}>
{this.state.count}
</button>
);
}
}
// Functional component
function MyComponent() {
const [count, setCount] = useState(0);
return <button onClick={() => setCount(count + 1)}>{count}</button>;
}
2) What’s the difference between props and state?
| Aspect | Props | State |
|---|
| Definition | Data passed from parent to child | Data managed within the component |
| Mutability | Immutable (read-only in child) | Mutable (via setState/useState) |
| Ownership | Owned by parent component | Owned by the component itself |
| Updates | Parent re-renders to pass new props | Component calls setState to update |
| Use case | Configuration, data flow down | Interactive data, component memory |
Key principles:
- Props flow down (unidirectional data flow) - parent controls child
behavior
- State is local - encapsulated within component unless lifted up
- Derive from props when possible - don’t copy props to state unnecessarily
// ❌ Bad: Copying prop to state (gets out of sync)
function User({ name }) {
const [userName, setUserName] = useState(name);
// userName won't update if parent changes name prop
}
// ✅ Good: Use prop directly or with useMemo for derivation
function User({ name }) {
const displayName = name.toUpperCase();
return <div>{displayName}</div>;
}
3) Why shouldn’t you mutate state directly?
Directly mutating state (e.g., state.value = 123) in React is a common
mistake. React relies on detecting changes to state objects to know when to
re-render components. If you mutate state directly, React may not detect the
change, leading to bugs and UI not updating as expected. Instead, always use the
state updater function (e.g., setValue(newValue)) or return a new object/array
when updating state.
Example:
// ❌ Bad: direct mutation
state.items.push(newItem);
setState(state);
// ✅ Good: create new array
setState({ ...state, items: [...state.items, newItem] });
4) How does React rendering work? What triggers re-renders and how do you avoid unnecessary ones?
- A component re-renders when its state changes or when its parent re-renders
and passes new props (new identities).
- React compares element trees (reconciliation) and updates the DOM minimally.
- To avoid unnecessary re-renders:
- Use React.memo for pure child components.
- Stabilize function and object props via useCallback/useMemo.
- Split large contexts; avoid putting high-churn data in a single provider.
- Derive UI from state instead of duplicating state.
- Virtualize long lists (react-window).
5) What is the Virtual DOM and how does React use it?
- The Virtual DOM (VDOM) is a lightweight JavaScript representation of the
actual DOM. It’s an in-memory tree of React elements.
- How React uses it:
- When state/props change, React creates a new VDOM tree
- React compares (diffs) the new tree with the previous one (reconciliation)
- React calculates the minimal set of DOM changes needed
- React batches and applies only those changes to the real DOM
Benefits:
- Performance: Batch updates and minimize expensive DOM operations
- Abstraction: Write declarative code without manual DOM manipulation
- Cross-platform: Same reconciliation algorithm works for React Native,
React Three Fiber, etc.
Common misconception: VDOM isn’t always faster than direct DOM manipulation.
For simple updates, direct DOM can be faster. VDOM’s value is in developer
productivity and handling complex UIs declaratively.
// You write declarative code:
<button onClick={() => setCount(count + 1)}>{count}</button>
// React handles the imperative DOM updates:
// button.textContent = newCount;
6) What are keys in lists and why are they critical?
- Keys give list items stable identities between renders, allowing React to
efficiently determine which items were added, removed, or reordered during
reconciliation.
- React uses keys to match old vs new children efficiently. Without keys (or
with duplicate keys), React may incorrectly reuse components, leading to state
bugs and performance issues.
- Best practices:
- Use stable, unique IDs from your data (e.g., database IDs)
- Avoid array indexes when items can be reordered, filtered, or
inserted/removed
- Never use random values (Math.random()) - keys must be stable across renders
- Common bugs with wrong keys:
- Input values appearing in wrong items after reordering
- Component state not properly resetting when items are replaced
- Animations/transitions breaking
// ❌ Bad: using index as key in dynamic list
{
items.map((item, i) => <Item key={i} {...item} />);
}
// ✅ Good: using stable unique ID
{
items.map((item) => <Item key={item.id} {...item} />);
}
7) How do you debug a React component that renders incorrectly?
- Check props and state: Use React DevTools to inspect the component tree
and see current props/state values.
- Add console.log: Log props, state, and key variables inside the render or
effect functions.
- Simplify the component: Temporarily remove logic or children to isolate
the problem.
- Check dependencies: Ensure useEffect/useMemo/useCallback dependencies are
correct.
- Check keys: For lists, make sure keys are unique and stable.
- Look for warnings: Read the browser console for React warnings or errors.
8) What is the difference between server-side rendering (SSR) and client-side rendering (CSR)?
- SSR: The HTML is generated on the server for each request, so the user
sees content faster and SEO is better. After loading, React hydrates the page
to make it interactive.
- CSR: The browser loads a mostly empty HTML file and JavaScript, then React
renders everything on the client. First paint is slower, but navigation can be
faster after the initial load.
When to use:
- SSR: For SEO, fast first paint, sharing links, or dynamic content.
- CSR: For apps where SEO is not important, or for highly interactive
dashboards.
9) What is Context? When to use it vs Redux Toolkit, Zustand, or React Query?
- Context removes prop drilling for relatively stable global values (theme,
locale, auth user, feature flags).
- Avoid using one giant context for rapidly changing values (causes wide
re-renders). Split contexts or use a state library.
- Redux Toolkit: predictable, centralized state with good devtools; best for
complex client state and cross-cutting concerns.
- Zustand/Jotai/Recoil: minimal APIs and fine-grained subscriptions; good for
simpler or performance-focused stores.
- React Query/SWR: manages server state (caching, refetching, retries). Don’t
put server data in Redux by default.
- Controlled: value is driven by state and updated via onChange. Best for
validation, conditional UI, and single source of truth.
- Uncontrolled: DOM holds value (ref). Simpler for basic forms, large forms
where full control adds overhead, or file inputs.
// Controlled
const [v, setV] = useState('');
<input value={v} onChange={(e) => setV(e.target.value)} />;
// Uncontrolled
const r = useRef(null);
<input ref={r} defaultValue="hi" />;
11) What are custom hooks and why would you create one?
A custom hook is a JavaScript function whose name starts with use and that can
call other hooks. Custom hooks let you extract and reuse stateful logic between
components (e.g., form handling, data fetching, subscriptions) without
duplicating code or using HOCs.
Example:
function useWindowWidth() {
const [width, setWidth] = useState(window.innerWidth);
useEffect(() => {
const onResize = () => setWidth(window.innerWidth);
window.addEventListener('resize', onResize);
return () => window.removeEventListener('resize', onResize);
}, []);
return width;
}
12) What is React.memo and when would you use it?
React.memo is a higher-order component that memoizes a functional component.
It prevents unnecessary re-renders by shallowly comparing props. Use it for pure
components that render the same output for the same props, especially in large
lists or performance-sensitive trees.
Example:
const MyComponent = React.memo(function MyComponent(props) {
// ...
});
- Use
React.memo for pure components. - Use
useMemo and useCallback to avoid unnecessary recalculations and
re-renders. - Virtualize long lists (e.g., with
react-window). - Code-split with
React.lazy and Suspense. - Debounce or throttle expensive operations.
- Avoid unnecessary state in high-level components.
- Profile with React DevTools and browser performance tools.
14) How and when to use useMemo and useCallback? What can go wrong?
- useMemo memoizes expensive computations; useCallback memoizes function
identities passed to children.
- Use them when profiling shows unnecessary re-renders or costly calculations.
- Pitfalls: overuse (adds complexity), wrong dependencies (stale results), and
assuming they always improve performance. Measure first.
15) Data fetching in React: useEffect vs React Query/SWR. Which is better and why?
- Raw useEffect is fine for simple fetches but you must handle caching, stale
data, retries, deduplication, background revalidation, and error states
manually.
- React Query/SWR handle these concerns out of the box, improving UX and
reliability. They also support optimistic updates and infinite queries.
16) What are React Fragments and why use them?
- Fragments let you group multiple children without adding extra DOM nodes.
Use
<React.Fragment> or the shorthand <>...</>.
Why use them:
- Avoid unnecessary wrapper divs that can break CSS layouts (flexbox/grid)
- Keep DOM cleaner and improve performance
- Required when returning multiple elements from a component
// ❌ Bad: extra div in DOM
return (
<div>
<h1>Title</h1>
<p>Content</p>
</div>
);
// ✅ Good: no wrapper element
return (
<>
<h1>Title</h1>
<p>Content</p>
</>
);
// With keys (use full syntax)
{
items.map((item) => (
<React.Fragment key={item.id}>
<dt>{item.term}</dt>
<dd>{item.desc}</dd>
</React.Fragment>
));
}
17) useRef — what is it and when do you use it?
useRef is a React Hook that returns a persistent, mutable object with a
.current property. This object stays the same between renders. Updating
ref.current does not cause a re-render, so refs are ideal for storing
values that need to survive across renders but shouldn’t trigger UI updates.
When to use useRef:
- To access and interact with DOM elements directly (e.g., focusing an input,
measuring size, scrolling).
- To keep mutable values like timers, interval IDs, or third-party library
instances.
- To store the latest value of a prop or callback, avoiding stale closures in
event handlers or effects.
Examples:
// Example 1: Keep previous value
function usePrevious(value) {
const ref = useRef();
useEffect(() => {
ref.current = value;
}, [value]);
return ref.current;
}
// Example 2: Timer ref in TypeScript
function Timer(): JSX.Element {
const intervalRef = (useRef < number) | (null > null);
useEffect(() => {
intervalRef.current = window.setInterval(() => console.log('tick'), 1000);
return () => {
if (intervalRef.current) window.clearInterval(intervalRef.current);
};
}, []);
return <div>Timer running</div>;
}
Key pitfalls:
- Don’t use refs for values that should trigger a UI update—use state for that.
ref.current is only set after the component mounts (undefined on the
server).
Common interview follow-up: If asked how to avoid stale closures in event
listeners, explain that you can store the latest callback in a ref and always
call ref.current from your event handler.
18) What are Higher-Order Components (HOCs)? When to use them vs hooks?
- HOC is a function that takes a component and returns a new component with
additional props or behavior. It’s a pattern for reusing component logic.
// Example HOC
function withAuth(Component) {
return function AuthComponent(props) {
const { user, loading } = useAuth();
if (loading) return <Spinner />;
if (!user) return <Redirect to="/login" />;
return <Component {...props} user={user} />;
};
}
// Usage
const ProtectedProfile = withAuth(Profile);
When to use HOCs:
- Legacy codebases already using them
- Need to wrap third-party components
- Enhancing components from libraries you don’t control
When to use hooks instead:
- Hooks are preferred in modern React - more composable, less nesting
- Better TypeScript support
- Easier to understand data flow
// ✅ Better: custom hook
function useAuth() {
const { user, loading } = useAuth();
return { user, loading };
}
function Profile() {
const { user, loading } = useAuth();
if (loading) return <Spinner />;
if (!user) return <Redirect to="/login" />;
return <div>Welcome {user.name}</div>;
}