In React, what is the argument you pass to createContext(defaultValue) used for, and in which situation does a component actually receive that default?
answer
- only one situation triggers it
- depends on the tree, not the value
- a provider passing undefined still wins
- context object identity does the matching
- no provider found anywhere above
basics
~20 screateContext's argument is the value a consumer receives when no matching Provider sits above it in the tree. If a Provider is present but passes undefined, the consumer gets undefined — the default is not a general fallback.
solid answer
~50 s`createContext(defaultValue)` returns a context object, and the argument is used in exactly one case: a component reads the context and React finds **no** Provider for that context object anywhere above it. React then returns the default instead of crashing. The common misconception is that the default acts as a fallback whenever the value is missing — it does not. `<ThemeContext value={undefined}>` is a perfectly good Provider, and consumers below it read `undefined`, not the default. So the default only protects against the "forgot to mount the Provider" case. That is why most teams pass a deliberately unusable default (`null` or `undefined`) for required context and detect it in a custom hook, and reserve a real default for genuinely optional context such as a theme. In React 19 you can render the context object directly: `<ThemeContext value="dark">`.
go deeper
Know that the argument to createContext is used only when a component finds no Provider above it, and that a Provider passing undefined hands consumers undefined.
Be ready to explain the nearest-provider walk and why context matching is by object identity, then justify choosing a real default versus null for optional versus required context.
Show you can diagnose the field failure: consumers reading the default despite a mounted provider, traced to two context objects from a duplicated module, and say how you would prevent it.
Frame the default as part of a module's public contract — whether a consumer may render standalone at all — and weigh the TypeScript and testing cost of a nullable context across many teams.
## What createContext actually creates `createContext` returns a **context object** — an opaque identity that React uses to match providers with readers: ```jsx import { createContext, useContext } from 'react'; const ThemeContext = createContext('light'); // 'light' is the default ``` In React 19 the context object itself renders as the provider: ```jsx <ThemeContext value="dark"> <Page /> </ThemeContext> ``` `<ThemeContext.Provider value="dark">` still works and is what you will see in older code. Reading is `useContext(ThemeContext)` or, in React 19, `use(ThemeContext)`. ## The lookup rule, precisely When a component reads a context, React walks **up its position in the rendered tree** looking for the nearest provider created from that exact context object, and returns that provider's `value` prop. The `defaultValue` you passed to `createContext` is returned only when that walk reaches the root without finding a provider. Two consequences follow. **First: `undefined` is a value like any other.** A provider that passes `value={undefined}` is still a provider, and it wins: ```jsx const ThemeContext = createContext('light'); <ThemeContext value={undefined}> <Button /> {/* useContext(ThemeContext) === undefined, NOT 'light' */} </ThemeContext> ``` This bites when a provider forwards a value that has not loaded yet: `<AuthContext value={user}>` before `user` exists hands every consumer `undefined`, and the sensible-looking default you wrote never appears. **Second: identity matters, not the variable name.** Every call to `createContext` produces a *different* context object. If the module defining the context is instantiated twice — two copies of a package in `node_modules`, a bundler that duplicates a barrel file, or an accidental second `createContext` call in a test helper — the provider registers one object and the consumer looks up another. The consumer finds no matching provider and silently receives the default. "My context always returns the default even though the provider is clearly mounted" is nearly always this. ## Choosing a default value There are three defensible choices, and the interview answer is knowing which one fits: 1. **A genuinely usable default** — `createContext('light')`. Right when the context is *optional* and the component tree is meant to work standalone: a theme, a locale, a density setting. A consumer rendered outside any provider still behaves sensibly, which also makes isolated tests and design-system showcases easy. 2. **`null` or `undefined` plus a checking hook** — `createContext(null)`. Right when the context is *required*: an auth session, a store handle, a form instance. There is no honest default for "the current user", so you make the absence detectable and report it at the point of misuse rather than letting `null` flow onward and blow up somewhere unrelated. 3. **A default whose members throw** — an object whose functions raise if called. Occasionally used by libraries that want reads to work but writes to fail loudly. It is more machinery than most codebases need. The hard rule is that whatever you pick, it is documentation for the "no provider" case only. It is not a merge, not a partial fill, and not a fallback for missing keys inside the value. ## TypeScript friction With `createContext<Session | null>(null)`, every consumer's type is `Session | null`, so every call site must narrow. That is the type system correctly telling you the provider might be missing — and it is the main practical reason teams funnel reads through one small wrapper hook that narrows once, rather than letting the nullable type spread across the app. ## What interviewers listen for They want you to say the default is used *only when no provider is found*, to correct the "fallback for undefined" misconception without prompting, and to connect the choice of default to whether the context is optional or required. Mentioning the duplicated-context-object diagnostic is a strong signal that you have debugged this in a real codebase.
- If the default is only used when no provider exists, why supply one at all for a required context?Mostly so the absence is representable and detectable. Passing `null` gives you a sentinel that a wrapper hook can test for and report on, and it forces TypeScript to model the "no provider" state. It is not there to make the component work — it is there to make the failure legible.
- A provider is clearly mounted, yet consumers keep reading the default. What do you check first?Context identity. Confirm the provider and the consumer import the same context object — one module instance, not two. Duplicate copies of a package, a duplicated barrel file, or a second `createContext` call in a test wrapper all produce two distinct contexts, and the consumer then legitimately finds no matching provider.
- Does calling use(SomeContext) instead of useContext change how the default is resolved?No. In React 19, `use(SomeContext)` performs the same nearest-provider lookup and returns the same default when none is found. What differs is where you may call it: `use` is allowed inside conditionals and loops, while `useContext` follows the usual top-level hook rule.
saying these in an interview costs you the question
- Says the default fills in whenever the value is undefined
- Thinks the default merges with the provider's value
- Believes the default applies before the provider mounts
- Assumes same variable name means same context object
- Claims a missing provider throws automatically