NgRx selectors beyond getting state: memoization, derived state, and Angular performance

HighIntermediateAngular
Preparing for interviews?

Use guided tracks for structured prep, then practice company-specific question sets when you want targeted interview coverage.

Quick Answer

NgRx selectors are not just state getters. They create memoized, composable read models that power derived state in Angular apps. Interviewers expect you to explain selector composition, NgRx selector memoization behavior, and why this improves performance.

Answer

Core idea

Selectors are your app’s read layer. Instead of pushing filtering/sorting/mapping logic into components, you compose selectors once and expose a memoized view model. This keeps components thin and avoids repeated recalculation.

Selector level

Example

Responsibility

Feature selector

selectProductsFeature

Grab one feature slice from root state

Entity/base selectors

selectEntities, selectIds

Read normalized raw state

Derived selectors

selectAllProducts, selectFilteredProducts

Transform/filter/sort domain data

VM selector

selectProductsVm

Return final UI-ready shape for component/template

Composed selector flow: feature -> entities -> filtered/sorted VM
TYPESCRIPT
import { createFeatureSelector, createSelector } from '@ngrx/store';

type Product = { id: string; name: string; price: number };
type SortKey = 'name-asc' | 'price-desc';

interface ProductsState {
  entities: Record<string, Product>;
  ids: string[];
  query: string;
  sort: SortKey;
  loading: boolean;
  error: string | null;
}

const selectProductsFeature = createFeatureSelector<ProductsState>('products');

const selectEntities = createSelector(selectProductsFeature, s => s.entities);
const selectIds = createSelector(selectProductsFeature, s => s.ids);
const selectQuery = createSelector(selectProductsFeature, s => s.query);
const selectSort = createSelector(selectProductsFeature, s => s.sort);
const selectLoading = createSelector(selectProductsFeature, s => s.loading);
const selectError = createSelector(selectProductsFeature, s => s.error);

const selectAllProducts = createSelector(selectIds, selectEntities, (ids, entities) =>
  ids.map(id => entities[id])
);

const selectFilteredProducts = createSelector(selectAllProducts, selectQuery, (products, query) => {
  const q = query.trim().toLowerCase();
  if (!q) return products;
  return products.filter(p => p.name.toLowerCase().includes(q));
});

const selectSortedProducts = createSelector(selectFilteredProducts, selectSort, (products, sort) => {
  const copy = [...products];
  if (sort === 'name-asc') return copy.sort((a, b) => a.name.localeCompare(b.name));
  return copy.sort((a, b) => b.price - a.price);
});

export const selectProductsVm = createSelector(
  selectSortedProducts,
  selectLoading,
  selectError,
  (items, loading, error) => ({ items, loading, error, total: items.length })
);
                  

Memoization behavior

What happens

Practical impact

First evaluation

Projector runs and result is cached

Expected initial compute cost

Same input selector references

Cached result returned, projector does not re-run

Avoids unnecessary CPU and re-renders

Any input reference changes

Projector re-runs once and cache is updated

Recompute only when data actually changed

Inputs recreated every time

Memoization is defeated

Performance degrades and UI churn increases

How NgRx selector memoization actually works
TYPESCRIPT
// ❌ Pitfall: selecting whole state and rebuilding arrays/objects in component
@Component({ selector: 'app-products', template: `...` })
export class ProductsComponent {
  // broad selection + local mapping each emission
  readonly vm$ = this.store.select(state => state).pipe(
    map(state => {
      const items = Object.values(state.products.entities)
        .filter(p => p.name.includes(state.products.query))
        .sort((a, b) => a.name.localeCompare(b.name));

      return {
        items: [...items], // new object/array every time
        total: items.length,
        loading: state.products.loading
      };
    })
  );
}

// ✅ Better: keep composition in selectors, component only selects VM
@Component({ selector: 'app-products', template: `...` })
export class ProductsComponent {
  readonly vm$ = this.store.select(selectProductsVm);
}
                  

Common pitfall

Why it hurts

Fix

Selecting the whole root state in components

Any unrelated state change can trigger unnecessary recalculation

Select the narrowest feature/VM selector

Doing filter/sort/map in components repeatedly

Logic duplication + unstable references + harder tests

Move derivation into composed selectors

Returning brand-new objects everywhere without need

Memoization cannot help if inputs are constantly rebuilt

Preserve stable references where possible and compose selectors

Keeping selectors too shallow (no VM selector)

Components become bloated orchestration layers

Create a single UI-focused VM selector

Selector performance pitfalls senior reviewers spot

Interview summary

NgRx selectors are a memoized read-model layer, not just property accessors. Compose selectors from feature state to entities to derived filtered/sorted data, then expose a final view model selector to components. This improves Angular selector performance, reduces component complexity, and keeps state logic reusable.

Similar questions
Guides
5 / 43