Interview answer drill

Use this Angular interview question to rehearse a quick answer, common mistake, follow-up, and production pitfall.

NgRx reducer pure function in practice: immutability, side effects, and common mistakesFrontend interview answer

HighIntermediateAngular
Interview focus

This Angular interview question tests whether you can explain Pure reducers in NgRx: immutability, forbidden side effects, and effects boundaries, connect it to production trade-offs, and handle common follow-up questions.

  • Pure reducers in NgRx: immutability, forbidden side effects, and effects boundaries explanation without falling back to memorized docs wording
  • Store and State Management reasoning, edge cases, and production failure modes
  • How you would answer the most likely Angular interview follow-up
Practice more Angular interview questions
Interview quick answer

An NgRx reducer must be deterministic, immutable, and side-effect free. The same state and action should always produce the same next state, while HTTP, router work, services, random values, and timestamps belong in effects instead.

Full interview answer

Deterministic reducer rule

An NgRx reducer is a synchronous, deterministic state transition function. The same input (state, action) must always produce the same next state, and it must do so without mutation or side effects. If it reaches outside to the network, router, services, random values, or timestamps, it is no longer a pure reducer.

Worked example

addTodo should return a new array based only on the previous state and the dispatched payload. It must not call Math.random(), fetch data, navigate, or ask a service for extra information. Those side effects belong in effects, not in the reducer.

Boundary rule

  • Reducers compute the next immutable state.
  • Effects handle HTTP, router work, timers, and other side effects.
  • Pure reducers keep state serializable, predictable, and testable.

Forbidden in reducers

Why it is wrong

Where it belongs

State mutation (push, direct assignment, in-place edits)

Breaks immutability and memoization assumptions

Return new objects/arrays in reducer

Math.random() / time-dependent values

Same input may produce different output

Generate IDs/timestamps before dispatch or in effects

HTTP calls

Async side effect inside supposedly pure function

Effects

Router navigation

External side effect not a state calculation

Effects or component/facade orchestration

Service calls (analytics, storage, logging APIs)

Leaks imperative side effects into reducer

Effects or dedicated services triggered elsewhere

Reducer purity guardrails
TYPESCRIPT
// ❌ Bad reducer: impure + mutating
export const ordersReducer = createReducer(
  initialState,
  on(placeOrder, (state, { item, router, api, audit }) => {
    state.orders.push(item); // mutation

    const id = Math.random().toString(36).slice(2); // non-deterministic

    api.postOrder(item).subscribe(); // HTTP side effect
    router.navigate(['/orders', id]); // navigation side effect
    audit.track('order_placed', item); // service side effect

    return state; // same reference returned
  })
);
                  
TYPESCRIPT
// ✅ Good reducer: pure + immutable
export interface OrdersState {
  orders: Array<{ id: string; name: string }>;
  loading: boolean;
  error: string | null;
}

export const initialState: OrdersState = {
  orders: [],
  loading: false,
  error: null
};

export const ordersReducer = createReducer(
  initialState,
  on(placeOrder, state => ({ ...state, loading: true, error: null })),
  on(placeOrderSuccess, (state, { order }) => ({
    ...state,
    orders: [...state.orders, order],
    loading: false
  })),
  on(placeOrderFailure, (state, { error }) => ({ ...state, error, loading: false }))
);
                  
TYPESCRIPT
// ✅ Side effects move to effects
@Injectable()
export class OrdersEffects {
  placeOrder$ = createEffect(() =>
    this.actions$.pipe(
      ofType(placeOrder),
      switchMap(({ draft }) =>
        this.api.postOrder(draft).pipe(
          map(order => placeOrderSuccess({ order })),
          catchError(err => of(placeOrderFailure({ error: String(err) })))
        )
      )
    )
  );

  navigateOnSuccess$ = createEffect(
    () =>
      this.actions$.pipe(
        ofType(placeOrderSuccess),
        tap(({ order }) => this.router.navigate(['/orders', order.id]))
      ),
    { dispatch: false }
  );

  constructor(
    private actions$: Actions,
    private api: OrdersApiService,
    private router: Router
  ) {}
}
                  

Reducer purity checklist

Pass condition

Deterministic output

Same state + action always yields same next state

No mutation

Original state untouched; new references returned

No async work

No HTTP/subscription/promise in reducer

No framework side effects

No router navigation, no service invocations

Synchronous and small

Just state transition logic, nothing else

Quick review rubric before merge

Interview summary

An NgRx reducer pure function does one thing: compute next immutable state. It must avoid mutation and all side effects, including Math.random(), HTTP, router calls, and service usage. Put side effects in effects, keep reducers deterministic, and selector memoization and debugging stay reliable.

Similar questions
Guides
Preparing for interviews?

Use this as one explanation rep, then continue with the Angular interview questions cluster or a guided prep path.