With Reselect's createSelector, what is the difference between input selectors and the result function, and when does the result function rerun?
answer
- extract first, then compute
- inputs run, results compared by reference
- same references, cached output
- arguments are checked before inputs
basics
~20 sInput selectors extract values from state and arguments; the result function computes the output from their results. It reruns only when the input results, compared by reference, match no cached combination; otherwise the cached output is returned.
solid answer
~40 s`createSelector([selectItems, selectCategory], (items, category) => items.filter(...))` builds a memoised **output selector**. The **input selectors** receive the same arguments as the output selector, `(state, ...args)`, and should only **extract**: return an existing piece of state or an argument. The **result function** receives their results and does the **transformation**. When the output selector is called, Reselect first checks whether the arguments themselves are identical to a cached call; if so, it returns the cached result immediately. Otherwise it runs every input selector and compares their results by reference: if all match a cached set, the result function is skipped and the previous output returned; if any differ, the result function runs. So the output keeps its reference across dispatches that do not touch the inputs.
code
ts · 18 linesimport { createSelector } from '@reduxjs/toolkit'
import { store, type RootState } from './store'
import { userRenamed } from './userSlice'
const selectItems = (state: RootState) => state.shop.items
const selectCategory = (_state: RootState, category: string) => category
export const selectItemsInCategory = createSelector(
[selectItems, selectCategory],
(items, category) => items.filter((i) => i.category === category),
)
const first = selectItemsInCategory(store.getState(), 'books')
store.dispatch(userRenamed('Ada'))
const second = selectItemsInCategory(store.getState(), 'books')
console.log(first === second) // true: items unchanged, result reused
console.log(selectItemsInCategory.recomputations()) // 1go deeper
Recall the shape: an array of input selectors, then a result function that receives their results in order.
Explain the two-stage check, arguments first and then input results by reference, and why inputs must extract while the result function transforms.
Prove memoisation with recomputations() in tests, compose selectors in layers, and read the development warnings that expose unstable inputs.
Set conventions for where derived data is computed so that selector layers stay cheap and cache-friendly as the store and the team grow.
## The three parts of createSelector Reselect's `createSelector` (also re-exported by Redux Toolkit) takes: 1. one or more **input selectors**, either as an array or as separate arguments; 2. a **result function** (also called the combiner), always the last function argument; 3. optionally, an **options object** to customise memoisation. It returns an **output selector**, a function you call exactly like a plain selector: `selectVisibleItems(state)` or `selectItemsInCategory(state, 'books')`. ```ts const selectItems = (state: RootState) => state.shop.items const selectCategory = (_state: RootState, category: string) => category export const selectItemsInCategory = createSelector( [selectItems, selectCategory], (items, category) => items.filter((i) => i.category === category), ) ``` ## Roles: extract versus transform | | Input selectors | Result function | |---|---|---| | Receives | the output selector's arguments, `(state, ...args)` | the input selectors' results, in order | | Should do | **extract**: return existing state or an argument | **transform**: filter, map, sort, combine | | Runs | on every call that is not an exact argument repeat | only when an input result changed | | Must return | a stable reference when the data is unchanged | the derived value | Every input selector receives **all** the arguments, so a selector that needs only the extra argument ignores the state: `(_state, category) => category`. ## What happens on each call Reselect memoises in two stages, which its docs call **cascading memoisation**: 1. **Argument check.** The output selector's arguments are compared with previous calls. If the exact same arguments (by reference) were seen, the cached result is returned without running anything. 2. **Input check.** Otherwise, every input selector runs, and their results are compared by reference (`===`) with the results that produced a cached output. If they all match, the cached output is returned and the result function is skipped. 3. **Recompute.** If any input result differs, the result function runs with the new results and its return value is cached. Because Redux produces a new root state object on every state-changing dispatch, stage 1 usually misses after a dispatch. Stage 2 is where memoisation earns its keep: a dispatch that changed `state.user` leaves `state.shop.items` as the same reference, so the filter is skipped and the output array keeps its identity. ## Why the split matters If you put the transformation in an input selector — ```ts createSelector([(state) => state.shop.items.filter(isVisible)], (items) => items) ``` — the input returns a new array on every call, stage 2 always sees a change, and the result function always reruns. The selector looks memoised but never hits its cache. Reselect 5's development checks warn about exactly this shape: `inputStabilityCheck` for an input selector that returns different results for the same arguments, and `identityFunctionCheck` for a result function that returns its input unchanged. ## Inspecting a selector Every output selector carries debugging fields: - `recomputations()` — how many times the result function has run; `resetRecomputations()` sets it back to zero; - `dependencyRecomputations()` — how many times the input selectors ran; - `lastResult()` — the last returned value; - `resultFunc` — the raw result function, useful to unit-test the computation without building state; - `dependencies` — the input selectors. A test that dispatches an unrelated action and asserts `recomputations()` did not increase is the cleanest proof that memoisation works. ## Composing selectors Output selectors are selectors, so they can be input selectors of other output selectors: - `selectVisibleItems` can feed `selectVisibleTotal`; - each layer only recomputes when its own inputs change. This keeps expensive derivations layered and cached without any component knowing about it. When a component needs several values at once, Reselect also exports `createStructuredSelector({ total: selectCartTotal, count: selectCartCount })`. It builds a memoised selector, using `createSelector` by default, whose result is an object of the input results; the object keeps its reference until one of those input results changes, which avoids returning a fresh object literal from a plain selector. The key rule to state in an interview: **inputs extract, the result function transforms, and the result function reruns only when an input's result changes by reference.**
- How does a Reselect input selector get the extra argument passed to the output selector?Every input selector receives all the arguments the output selector was called with. An input that needs only the extra argument ignores the state, as in `(_state, category) => category`, and its result is then passed to the result function next to the extracted state.
- How do you unit-test the computation inside a Reselect selector without building a whole Redux state?Call `selector.resultFunc(...)` directly with the values the inputs would return, such as `selectItemsInCategory.resultFunc(items, 'books')`. It is the raw result function, unmemoised, so the test covers only the transformation.
saying these in an interview costs you the question
- The result function runs on every call to the selector
- Input selectors are compared by deep equality
- Filtering inside an input selector is as good as in the result function
- Input selectors only receive the state, not extra arguments
- createSelector caches the input selectors' own logic