Episode 2 — React Frontend Architecture NextJS / 2.2 — React Components and Props
Interview Questions: React Components & Props
These questions test conceptual depth, practical application, and the ability to explain React fundamentals clearly — exactly what interviewers look for.
How to Use This Material
- Answer out loud — practice explaining concepts verbally, as you would in an interview.
- Start with the "why" — interviewers value understanding of why something works, not just what it does.
- Use concrete examples — always support your answer with a code snippet or analogy.
- Know the follow-ups — each question lists common follow-up questions interviewers ask.
- Time yourself — aim for 2-3 minute answers. Concise > verbose.
Beginner (Q1–Q6)
Q1. What is a React component? How are functional components different from class components?
Why interviewers ask: They want to know if you understand React's core building block and whether you know the current standard (functional) vs. legacy approach (class).
Model answer: A React component is a reusable, self-contained piece of UI that accepts inputs (props) and returns JSX describing what should appear on screen. Conceptually, components are JavaScript functions: they take data in and return a view.
Functional components are plain functions that return JSX. Class components extend React.Component and use a render() method. In 2026, functional components with hooks are the standard — class components are legacy. The key differences:
- Functional: Simpler syntax, use hooks (
useState,useEffect) for state and side effects, easier to test, better tree-shaking - Class: Use
this.state,this.setState(), lifecycle methods (componentDidMount), more boilerplate, harder to reuse logic between components
// Functional (modern)
function Greeting({ name }) {
return <h1>Hello, {name}!</h1>;
}
// Class (legacy)
class Greeting extends React.Component {
render() {
return <h1>Hello, {this.props.name}!</h1>;
}
}
React team recommends functional components for all new code.
Q2. What are props in React? How do they differ from state?
Why interviewers ask: Props vs. state confusion is the #1 beginner mistake. They want to verify your mental model of data flow.
Model answer:
Props (properties) are inputs passed to a component by its parent, like function arguments. They are read-only — a component cannot modify its own props. State is data owned and managed by the component itself using useState, and it can be changed via a setter function.
Key differences:
| Aspect | Props | State |
|---|---|---|
| Who owns it | Parent | Component itself |
| Mutable? | No (read-only) | Yes (via setState) |
| Triggers re-render? | Yes, when parent sends new values | Yes, when updated |
| Used for | Configuration, data from parent | Interactive/dynamic data |
Both props and state changes trigger re-renders. Props flow down (parent → child), while state is local. When a child needs to communicate back to a parent, the parent passes a callback function as a prop.
Q3. Why are keys important when rendering lists in React?
Why interviewers ask: Keys are a common source of bugs. They test whether you understand React's reconciliation algorithm.
Model answer: Keys help React identify which list items changed, were added, or removed between renders. Without keys, React can only compare items by their position in the array, leading to unnecessary DOM updates and state bugs.
When you remove the first item from a list of 1,000 items:
- Without keys: React updates all 999 remaining items (thinks each one "changed") and deletes the last one — 1,000 DOM operations
- With keys: React removes the one DOM node with the deleted key — 1 DOM operation
Keys must be stable (same item always gets same key) and unique among siblings. Database IDs are ideal. Array indices are problematic when items can be reordered, inserted, or deleted, because stateful components get mismatched state.
Q4. What is the children prop? Give three different use cases.
Why interviewers ask: Understanding children tests your grasp of composition — React's primary reuse mechanism.
Model answer:
The children prop is a special built-in prop that contains whatever you place between a component's opening and closing tags. It's React's version of "slots" in other frameworks.
Three use cases:
- Wrapper/Layout components — A
PageLayoutthat adds header/footer around any content:
function PageLayout({ children }) {
return <><Header /><main>{children}</main><Footer /></>;
}
- UI containers — A
ModalorCardthat provides visual styling around arbitrary content:
<Modal title="Confirm"><p>Are you sure?</p><Button>Yes</Button></Modal>
- Named slots via multiple props — When you need multiple insertion points:
<Layout header={<Navbar />} sidebar={<Menu />}>{mainContent}</Layout>
children can be strings, numbers, JSX elements, arrays, or even functions (render props pattern).
Q5. What is JSX? Does the browser understand it?
Why interviewers ask: They want to know if you understand the toolchain, not just the syntax.
Model answer:
JSX is a syntax extension for JavaScript that looks like HTML but gets compiled to React.createElement() calls. The browser doesn't understand JSX — a build tool (Babel, SWC, TypeScript compiler) transforms it before the code reaches the browser.
// What you write (JSX):
<Button variant="primary" onClick={handleClick}>Submit</Button>
// What the compiler produces:
React.createElement(Button, { variant: "primary", onClick: handleClick }, "Submit")
JSX rules differ from HTML: className instead of class, htmlFor instead of for, self-closing tags required (<img />), all expressions in curly braces {}, and components must be capitalized.
Q6. What is one-way data flow in React? Why does React enforce it?
Why interviewers ask: This tests your understanding of React's fundamental architecture principle.
Model answer: One-way data flow (unidirectional data flow) means data in React flows in a single direction: from parent to child via props. Children cannot send data back up by modifying props — they can only signal parents via callback functions passed as props.
React enforces this because:
- Predictability — You can trace any piece of data to its source by following the component tree upward
- Debugging — When something's wrong, you know the data came from a parent, not from a sibling or child
- Performance — React can efficiently determine what needs to re-render because the flow is clear
The alternative (two-way binding, as in early Angular) allows children to modify parent state directly, leading to circular dependencies and hard-to-trace bugs.
Intermediate (Q7–Q12)
Q7. Explain the difference between controlled and uncontrolled inputs. When would you use each?
Why interviewers ask: Form handling is a daily task. They want to know you understand the tradeoffs.
Model answer: In a controlled input, React state is the "source of truth" — the input's value is always driven by state, and every change goes through a state updater:
const [email, setEmail] = useState("");
<input value={email} onChange={e => setEmail(e.target.value)} />
In an uncontrolled input, the DOM is the source of truth — you read the value using a ref when needed:
const inputRef = useRef();
<input ref={inputRef} defaultValue="" />
// Read: inputRef.current.value
Use controlled when you need: real-time validation, conditional formatting, dependent fields, or the value elsewhere in the UI. Use uncontrolled for: simple forms where you only need values on submit, integration with non-React code, or file inputs (which must be uncontrolled).
Q8. What is prop drilling and how do you solve it?
Why interviewers ask: Every non-trivial React app hits this problem. They want to know your solutions.
Model answer: Prop drilling is when you pass props through intermediate components that don't use the data — they just forward it to deeper children. If a value needs to go 5 levels deep, all 4 intermediate components must include it in their signatures.
Problems: verbosity, fragility when refactoring, noise in component APIs, testing overhead.
Solutions (from simplest to most complex):
- Component composition — restructure so the consuming component is closer to the data source; pass already-rendered elements as children
- Context API — skip intermediate components entirely; consumers access data directly
- State management libraries — Zustand, Redux for complex shared state
I reach for composition first, Context for genuine cross-cutting concerns (theme, auth, locale), and Zustand/Redux only when Context isn't enough.
Q9. You have a list of items with checkboxes. When you delete the first item, the wrong checkbox is checked. What's happening and how do you fix it?
Why interviewers ask: This is the classic index-as-key bug. They want to see if you can diagnose and fix it.
Model answer: This is caused by using array indices as keys. When you delete item 0, all remaining items shift position: item 1 becomes index 0, item 2 becomes index 1, etc. But React's reconciler sees "key=0 still exists" and reuses the component at that position, preserving its state (the checkbox). So the checkbox state from the deleted item transfers to the next item.
Fix: Use a stable, unique identifier as the key — typically the item's database ID:
// Before (buggy)
items.map((item, index) => <TodoItem key={index} ... />)
// After (correct)
items.map(item => <TodoItem key={item.id} ... />)
Now when item with id=1 is deleted, React sees key=1 is gone and destroys that component. Keys 2 and 3 are matched correctly and keep their state.
Q10. How would you design a reusable Button component for a design system?
Why interviewers ask: Component API design is a core skill. They want to see how you think about flexibility vs. simplicity.
Model answer: I'd design the API around the most common use cases while keeping it extensible:
interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
variant?: "primary" | "secondary" | "danger" | "ghost";
size?: "small" | "medium" | "large";
loading?: boolean;
icon?: React.ReactNode;
iconPosition?: "left" | "right";
fullWidth?: boolean;
children: React.ReactNode;
}
Key decisions:
- Extend native button attributes — so
disabled,type,onClick,aria-*all work automatically - Variant over individual style props —
variant="danger"notcolor="red" bold borderColor="red" loadingas a boolean — shows spinner and disables automatically- Forward ref — so parent can focus the button programmatically
- Spread rest props — anything not explicitly handled passes to the native
<button>
I'd avoid: too many props (sign the component does too much), inline styles (hard to override), and boolean props for things that should be variants.
Q11. What is the spread operator for props? What are its benefits and dangers?
Why interviewers ask: Spread is powerful but can introduce subtle bugs. They want to know you use it wisely.
Model answer:
The spread operator {...obj} passes all properties of an object as individual props:
const props = { name: "Alice", age: 28 };
<Component {...props} />
// Same as: <Component name="Alice" age={28} />
Benefits:
- Forwarding native HTML attributes to underlying elements
- Passing configuration objects cleanly
- Rest/spread pattern:
const { label, ...rest } = props; <input {...rest} />
Dangers:
- Order matters —
{...defaults}thenoverride="value"works, but reversed, the spread overwrites - Unknown props on DOM — spreading API response data onto a DOM element causes React warnings and potential XSS
- Readability — hard to know what props a component receives when everything is spread
- Performance — creates new objects on each render
Rule: spread props you've explicitly defined (like forwarding to native elements), never spread untrusted data.
Q12. How do you handle errors in a list when one item's data is malformed?
Why interviewers ask: Real-world data is messy. They want to see defensive coding.
Model answer: Three strategies:
- Filter before rendering — remove malformed items:
const validUsers = users.filter(u => u && u.id && u.name);
validUsers.map(u => <UserCard key={u.id} user={u} />);
- Guard inside the item component — handle missing data gracefully:
function UserCard({ user }) {
if (!user?.name) return null; // Skip rendering
return <div>{user.name}</div>;
}
- Error boundary — catch rendering errors from individual items so one bad item doesn't crash the whole list:
items.map(item => (
<ErrorBoundary key={item.id} fallback={<li>Error loading item</li>}>
<ListItem item={item} />
</ErrorBoundary>
));
I prefer combining filter (for known data issues) with error boundaries (for unexpected failures).
Advanced (Q13–Q18)
Q13. Explain the compound component pattern. When would you use it over a flat props API?
Why interviewers ask: This tests advanced component design skills and understanding of composition.
Model answer:
The compound component pattern groups related components under a parent namespace. Sub-components are attached as static properties: Card.Header, Card.Body, Card.Footer.
<Card>
<Card.Header title="Settings" />
<Card.Body><form>...</form></Card.Body>
<Card.Footer><Button>Save</Button></Card.Footer>
</Card>
vs. flat props:
<Card title="Settings" body={<form>...</form>} footerContent={<Button>Save</Button>} />
Use compound components when:
- The component has multiple distinct visual sections
- Users need flexibility in which sections to include and their order
- The prop count would exceed 8-10 with a flat API
- Sub-components need to share internal state (via Context)
Keep flat props when:
- The component is simple (3-5 props)
- Layout is always the same
- There's no section flexibility needed
Real examples: Reach UI's Menu/Menu.Button/Menu.List, Radix UI primitives, Headless UI.
Q14. How does React's reconciliation algorithm work for lists? Walk through the key-based diffing process.
Why interviewers ask: Deep framework knowledge separates mid-level from senior engineers.
Model answer: When React re-renders a list, it runs a two-phase reconciliation:
Phase 1: Build key maps
React creates a map from the old list: { key: fiber } for each child.
Phase 2: Walk the new list For each child in the new list:
- Look up its key in the old map
- If found → reuse the existing fiber (DOM node), update only changed props
- If not found → create a new fiber (insert new DOM node)
Phase 3: Clean up Any keys remaining in the old map that weren't matched → delete those fibers (remove DOM nodes)
This is O(n) where n is the list length. Without keys, React falls back to position-based comparison, which can cause O(n) DOM mutations for a single insertion/deletion at the beginning.
Critical detail: React does NOT reorder DOM nodes by default — it prefers to update-in-place. When keys indicate reordering, React uses a "placeChild" algorithm to determine the minimum number of moves.
Q15. You're building a component library. How do you make a component that can render as different HTML elements (polymorphic component)?
Why interviewers ask: This is a real design system challenge that tests TypeScript and API design skills.
Model answer: Use the "as" prop pattern:
type PolymorphicProps<E extends React.ElementType> = {
as?: E;
children: React.ReactNode;
} & Omit<React.ComponentPropsWithoutRef<E>, "as" | "children">;
function Text<E extends React.ElementType = "p">({
as,
children,
...props
}: PolymorphicProps<E>) {
const Component = as || "p";
return <Component {...props}>{children}</Component>;
}
// Usage — fully type-safe
<Text>Default paragraph</Text>
<Text as="h1" className="title">Heading</Text>
<Text as="a" href="/about">Link</Text> // href is type-checked!
<Text as="label" htmlFor="email">Label</Text>
The TypeScript generic E extends React.ElementType ensures that when as="a", only valid <a> attributes are accepted. This is the same pattern used by Chakra UI, styled-components, and Radix.
Q16. How would you optimize a list of 10,000 items for rendering performance?
Why interviewers ask: Performance optimization at scale is a senior-level concern.
Model answer: For 10,000 items, rendering all of them at once would create 10,000+ DOM nodes — slow initial paint, high memory, janky scrolling.
Solution 1: Virtualization (primary)
Use a windowing library (react-window or @tanstack/virtual) that only renders items visible in the viewport plus a small buffer:
import { FixedSizeList } from "react-window";
<FixedSizeList height={600} itemSize={50} itemCount={10000}>
{({ index, style }) => <div style={style}>{items[index].name}</div>}
</FixedSizeList>
This renders ~15 DOM nodes instead of 10,000.
Solution 2: Pagination Show 50 items per page with page controls. Simpler but less fluid.
Solution 3: React.memo on list items
Prevent re-rendering items whose props haven't changed:
const MemoizedItem = React.memo(ListItem);
Solution 4: Stable keys and callbacks
Use proper keys (database IDs) and useCallback for event handlers passed to items.
I'd combine virtualization + memo + stable keys for the best result.
Q17. Explain the render prop pattern vs. the custom hook pattern for sharing logic. When would you choose each?
Why interviewers ask: This tests evolution of React patterns and ability to choose the right abstraction.
Model answer: Both patterns extract reusable logic, but they differ in how consumers access the data:
Render prop — passes data via a function-as-child:
<MousePosition>{({ x, y }) => <div>Position: {x}, {y}</div>}</MousePosition>
Custom hook — returns data from a function call:
function MyComponent() {
const { x, y } = useMousePosition();
return <div>Position: {x}, {y}</div>;
}
When to use hooks (default in 2026):
- Sharing stateful logic between components
- Cleaner syntax, better composability
- Can use multiple hooks without nesting
- TypeScript inference works naturally
When render props still make sense:
- You need to control WHAT renders, not just the data (component-level inversion of control)
- Libraries that need to work with class components
- Compound components sharing internal state with children
Hooks largely replaced render props, but the pattern still exists in libraries like Downshift and Formik.
Q18. How would you design a component that fetches data and handles loading, error, and empty states — without an external library?
Why interviewers ask: This combines hooks, conditional rendering, error handling, and UX thinking.
Model answer:
I'd create a generic useAsync hook and a presentation layer:
function useAsync(asyncFn, deps = []) {
const [state, setState] = useState({ data: null, error: null, loading: true });
useEffect(() => {
let cancelled = false;
setState(s => ({ ...s, loading: true, error: null }));
asyncFn()
.then(data => { if (!cancelled) setState({ data, error: null, loading: false }); })
.catch(error => { if (!cancelled) setState({ data: null, error, loading: false }); });
return () => { cancelled = true; };
}, deps);
return state;
}
function UserList() {
const { data: users, loading, error } = useAsync(() =>
fetch("/api/users").then(r => r.json())
, []);
if (loading) return <Skeleton count={5} />;
if (error) return <ErrorState message={error.message} onRetry={() => window.location.reload()} />;
if (!users?.length) return <EmptyState icon="👥" title="No users found" />;
return (
<ul>
{users.map(user => <UserCard key={user.id} user={user} />)}
</ul>
);
}
Key design decisions:
- Cleanup on unmount (
cancelledflag) prevents state updates on unmounted components - Loading skeleton over spinner for better perceived performance
- Error with retry — always give users a recovery path
- Empty state — explicit handling, not just a blank screen
- Separation — hook handles data, component handles presentation
In production, I'd use TanStack Query for caching, background refetching, and retry logic.
Total: 18 questions (6 Beginner, 6 Intermediate, 6 Advanced)
Practice time: Answer each question in 2-3 minutes for interview simulation