Master React props — immutability, one-way data flow, lifting state up, the children prop, prop drilling, and the TypeScript patterns for typing props correctly in every interview scenario.
Props are a component's function arguments — read-only data that flow from parent to child. State is a component's private memory — data it owns and can change. React enforces one-directional data flow: data only moves down through props; changes move back up through callback functions passed as props. This one-way street is what makes React apps predictable — you always know where data lives, and there is exactly one way it can change.
A React component is a function. Props are its arguments. Just as you can't modify a function argument inside the function body without side effects, a component must never modify its props:
// Component receives props like function arguments
function Greeting({ name, age }) {
// ❌ Never do this — props are read-only
name = 'Bob'; // this is a local mutation, doesn't affect the parent anyway
return <p>Hello, {name}. You are {age} years old.</p>;
}
// Parent controls what props the child receives
<Greeting name="Alice" age={30} />
If a component receives an incorrect prop, the fix is to change what the parent passes — not to modify the prop inside the child.
Data in React flows in one direction: parent → child via props. A child component cannot directly change its parent's state. This makes the data flow predictable:
function Parent() {
const [count, setCount] = useState(0);
// Data flows DOWN via props
return <Child count={count} onIncrement={() => setCount(c => c + 1)} />;
}
function Child({ count, onIncrement }) {
// Child reads data from props — cannot directly change parent state
// Changes move UP via callback props
return <button onClick={onIncrement}>Count: {count}</button>;
}
To send data "up" the tree, a parent passes a callback function as a prop. The child calls the callback with the new value; the parent updates its state; React re-renders both with the new value. This is the only correct way for a child to influence its parent.
When two sibling components need to share state, the state must live in their closest common ancestor — this is "lifting state up":
// ❌ Each component has its own state — they can't share it
function TempInput() { const [val, setVal] = useState(0); ... }
function TempDisplay() { const [val, setVal] = useState(0); ... } // separate, can't sync
// ✅ State lifted to the parent — both siblings share the same source of truth
function TemperatureConverter() {
const [celsius, setCelsius] = useState(0);
return (
<>
<CelsiusInput value={celsius} onChange={setCelsius} />
<FahrenheitDisplay celsius={celsius} />
</>
);
}
Any JSX you nest between a component's opening and closing tags becomes its children prop. This is how composable layout components are built:
function Card({ title, children }) {
return (
<div className="card">
<h2>{title}</h2>
<div className="card-body">{children}</div>
</div>
);
}
// Anything between <Card> tags becomes children
<Card title="User Profile">
<Avatar src={user.avatarUrl} />
<p>{user.bio}</p>
</Card>
Prop drilling is when a prop is passed through multiple intermediate components that don't use it — they just relay it to a deeper child. It becomes a maintenance problem when the prop needs to reach many levels deep:
// Prop drilling — userId passes through App → Layout → Sidebar → UserMenu
// Layout and Sidebar don't need userId at all
function App() {
return <Layout userId={user.id} />;
}
function Layout({ userId }) {
return <Sidebar userId={userId} />;
}
function Sidebar({ userId }) {
return <UserMenu userId={userId} />;
}
function UserMenu({ userId }) {
return <a href={`/users/${userId}`}>Profile</a>;
}
Solutions: React Context (for ambient data read by many components), component composition (pass the rendered element as a prop instead of the data), or state management libraries for complex cases.
Always type props explicitly — it serves as documentation and catches mistakes at compile time:
// Inline type — good for simple components
function Button({ label, onClick, disabled = false }: {
label: string;
onClick: () => void;
disabled?: boolean; // optional prop — ? makes it optional, with default
}) {
return <button onClick={onClick} disabled={disabled}>{label}</button>;
}
// Interface — good for larger or reused prop shapes
interface UserCardProps {
user: {
id: string;
name: string;
avatarUrl?: string; // optional
};
onSelect: (id: string) => void;
className?: string;
}
function UserCard({ user, onSelect, className }: UserCardProps) {
return (
<div className={className} onClick={() => onSelect(user.id)}>
{user.name}
</div>
);
}
A simple framework for deciding where data should live:
Many developers think they can mutate props to update the UI — props are read-only in the component that receives them. Mutating a prop changes a local reference; it doesn't update the parent's state or trigger a re-render. To change what a child displays, the parent must pass different props.
Many developers confuse one-way data flow with 'children can never affect parents' — children CAN affect parents through callback props. The data (state) lives in the parent; the child calls the callback to request a change; the parent decides whether and how to update its state. Data still flows down; control flows up through function calls.
Many developers think prop drilling is unavoidable and immediately reach for Context or Redux — often the right fix for prop drilling is composition: instead of drilling userId down through Layout, have App render UserMenu directly inside Layout via children. Restructuring the component tree often eliminates drilling without adding Context.
Many developers think the children prop is special React magic — children is just a regular prop whose value is whatever JSX you put between the component's open and closing tags. It works identically to any other prop; you can even rename it: function Modal({ content }) { ... } and pass it as <Modal content={<p>hi</p>} />.
Many developers add defaultProps to functional components — defaultProps is deprecated for function components (and removed in React 19 for function components). Use JavaScript default parameter values instead: function Btn({ label = 'Click me' }). This is simpler, works with TypeScript, and is the current standard.
Many developers think passing too many props (10+) always means the component needs refactoring — sometimes a complex component genuinely needs many inputs. The issue is when props are structurally tangled (a prop only makes sense in combination with another). Group related props into an object, or break the component into smaller pieces, but don't refactor just because the prop count is high.
Design system components: a Button component has props for variant, size, disabled, onClick, and children. Its strict prop interface means every consumer is forced to use the component the intended way — no rogue inline styles.
Form field components: a controlled TextField takes value, onChange, label, error, and helperText as props. The parent form owns the state; the TextField is purely presentational — it receives data and emits changes via callbacks.
List items in a data table: a TableRow receives the row data object as a prop and an onSelect callback. The parent Table manages which row is selected; the TableRow calls onSelect when clicked, lifting the selection event up.
Compound components: a Tabs component passes the selected tab index and an onChange callback to TabList and TabPanel children via the children prop pattern — the parent Tabs owns the state, the children are controlled.
React Router Link: the to prop controls where the link navigates. This is a standard controlled-via-props pattern — the Link component has no opinion about destination; the parent tells it where to go.
Next.js page components: page components receive params (route params), searchParams (query string), and no other React props — Next.js passes route information as props, demonstrating how framework data flows into components via the standard props mechanism.
What are props and how is prop drilling a problem?
What is the difference between state and props?
What is the Render Props pattern and when would you still use it?
Render props — caller controls rendering
Reading answers is not the same as knowing them. Practice saying them out loud with AI feedback — that's what builds real interview confidence.