NgRx Interview Questions and Answers
34 hand-picked NgRx interview questions with
detailed answers. Open the interactive version above to search, filter
by difficulty, run code, bookmark questions and track your progress.
Explain the NgRx flow: store, action, reducer, selector, effect.
A Redux pattern with one-way data flow:
- Store — single immutable source of truth.
- Action — a plain event describing what happened.
- Reducer — pure function
(state, action) → new state, no side effects. - Selector — memoised query to read a slice of state.
- Effect — handles side effects (API calls); listens for an action, does async work, dispatches success/failure.
Component dispatches → effect calls API → dispatches success → reducer updates store → selector pushes the new value to the component.
// 1. Component dispatches
this.store.dispatch(loadUsers());
// 2. Effect hears it, calls the API, dispatches the result
loadUsers$ = createEffect(() => this.actions$.pipe(
ofType(loadUsers),
switchMap(() => this.api.getUsers().pipe(
map(users => loadUsersSuccess({ users })),
catchError(error => of(loadUsersFailure({ error })))
))
));
// 3. Reducer folds the success action into new state
on(loadUsersSuccess, (state, { users }) => ({ ...state, users, loading: false }))
// 4. Selector pushes it back to the component
users$ = this.store.select(selectUsers);
When would you NOT use NgRx?
NgRx adds boilerplate. Skip it for small apps or local state — a simple service with a BehaviorSubject (or Signals) is enough. Reach for NgRx when state is large, shared across many features, and needs traceability / undo / debugging.
Why must reducers be pure and immutable?
Purity (same input → same output, no side effects) makes state predictable, testable, and time-travel debuggable. Immutability (return a new object, never mutate) lets the store and OnPush components detect changes by reference, keeping change detection fast.
on(loadSuccess, (state, { items }) => ({ ...state, items, loading: false }))
What are selectors and why are they memoised?
Selectors are pure functions that read and derive data from the store. createSelector memoises the result — it recomputes only when its inputs change, avoiding needless recalculation and re-renders.
export const selectActiveUsers = createSelector(
selectUsers,
users => users.filter(u => u.active)
);
What are Effects and why not call APIs in reducers?
Effects isolate side effects (HTTP, routing, storage) from reducers. An effect listens to an action stream, runs async work, and maps it to a new action. Reducers must stay pure, so any I/O belongs in effects.
load$ = createEffect(() => this.actions$.pipe(
ofType(load),
switchMap(() => this.api.get().pipe(
map(items => loadSuccess({ items })),
catchError(err => of(loadFailure({ err })))
))
));
NgRx store vs a service with BehaviorSubject.
A service + BehaviorSubject is simple shared state — great for small/medium apps. NgRx adds structure: strict unidirectional flow, DevTools time-travel, effects, and clear conventions that scale to large teams — at the cost of boilerplate.
What is @ngrx/entity?
A helper that stores collections in a normalised { ids: [], entities: {} } shape and provides an adapter with CRUD helpers (addOne, updateOne, removeOne) plus ready-made selectors — removing hand-written immutable array logic.
const adapter = createEntityAdapter<User>();
const initialState = adapter.getInitialState();
What do the Redux DevTools give you with NgRx?
A visual log of every action, the state before/after, and time-travel debugging — you can replay actions and jump to any past state. This traceability is a core reason to adopt NgRx on large apps.
StoreDevtoolsModule.instrument({ maxAge: 25 })
What's a good convention for naming actions?
Use the pattern [Source] Event — describe what happened, not what to do. Group by feature and event (e.g. [Auth API] Login Success). Prefer many specific, descriptive actions over a few generic setters.
export const loginSuccess = createAction(
'[Auth API] Login Success',
props<{ user: User }>()
);
What are Actions in NgRx? How do you create them using createAction()?
Actions are plain objects that describe what happened in the application. They are the only way to change the store's state. Modern NgRx uses createAction() to define actions, optionally with props() to carry data.
export const loadUsers = createAction('[Users Page] Load Users');
export const loadUsersSuccess = createAction(
'[Users API] Load Success',
props<{ users: User[] }>()
);
What is props() in NgRx?
props() defines the payload carried by an action. Instead of manually creating an object, it provides strongly typed data that reducers and effects can safely access.
export const updateUser = createAction(
'[User Page] Update User',
props<{ id: number; name: string }>()
);
this.store.dispatch(updateUser({
id: 1,
name: 'John'
}));
Why is createReducer() preferred over the old switch statement?
createReducer() is more readable, type-safe and easier to maintain. It avoids long switch statements and works seamlessly with on() handlers. It also provides better TypeScript support and cleaner code.
export const userReducer = createReducer(
initialState,
on(loadUsersSuccess, (state, { users }) => ({
...state,
users
}))
);
What is the on() function inside createReducer()?
on() connects one or more actions to a reducer function. Whenever one of those actions is dispatched, the reducer executes and returns a new immutable state.
export const reducer = createReducer(
initialState,
on(loadUsersSuccess, (state, { users }) => ({
...state,
users,
loading: false
}))
);
When should you use switchMap(), mergeMap(), concatMap() and exhaustMap() inside NgRx Effects?
Each RxJS mapping operator has a different behavior:
- switchMap() — Cancels the previous request and keeps only the latest one. Best for Search or Auto Complete.
- mergeMap() — Runs all requests in parallel. Best when every request must complete independently.
- concatMap() — Executes requests one after another in order. Best for sequential updates.
- exhaustMap() — Ignores new requests while one is already running. Best for Login or Submit buttons to prevent duplicate requests.
// Search API
switchMap(() => this.api.search())
// Multiple parallel uploads
mergeMap(() => this.api.upload())
// Save records sequentially
concatMap(() => this.api.save())
// Prevent double login click
exhaustMap(() => this.api.login())
How do you handle API Success and Failure in NgRx?
Follow a three-action pattern: Load, Load Success, and Load Failure. The component dispatches the Load action, the Effect calls the API, then dispatches either Success or Failure. The Reducer updates the store accordingly.
export const loadUsers = createAction('[Users] Load');
export const loadUsersSuccess = createAction(
'[Users API] Load Success',
props<{ users: User[] }>()
);
export const loadUsersFailure = createAction(
'[Users API] Load Failure',
props<{ error: any }>()
);
What is the difference between the Root Store and a Feature Store?
The Root Store contains global application state (authentication, user, settings). A Feature Store contains state for a specific feature or module (Products, Orders, Dashboard). Feature stores are registered independently and keep the application modular.
StoreModule.forRoot({ auth: authReducer });
StoreModule.forFeature('products', productReducer);
How does NgRx work with Lazy Loaded Modules?
Each lazy-loaded module can register its own reducer and effects using StoreModule.forFeature() and EffectsModule.forFeature(). The feature state is loaded only when the module is visited, improving startup performance.
@NgModule({
imports: [
StoreModule.forFeature('orders', orderReducer),
EffectsModule.forFeature([OrderEffects])
]
})
export class OrdersModule {}
What is createFeatureSelector() and why is it used?
createFeatureSelector() selects an entire feature state from the store. Other selectors are then composed from it using createSelector(). This improves code organization and reusability.
export const selectUserState = createFeatureSelector<UserState>('users');
export const selectUsers = createSelector(
selectUserState,
state => state.users
);
What is the difference between createFeatureSelector() and createSelector()?
createFeatureSelector() selects an entire feature slice from the store. createSelector() derives or computes smaller pieces of data from one or more selectors. In practice, you usually create one feature selector first, then build multiple selectors on top of it.
const selectProductState = createFeatureSelector<ProductState>('products');
const selectProducts = createSelector(
selectProductState,
state => state.products
);
const selectProductCount = createSelector(
selectProducts,
products => products.length
);
How do you reset the NgRx Store when a user logs out?
Dispatch a logout action and reset the state inside the root reducer (or a meta reducer) by returning the initialState. This clears all user-specific data from the store.
export const logout = createAction('[Auth] Logout');
export const reducer = createReducer(
initialState,
on(logout, () => initialState)
);
How do you persist the NgRx Store after a page refresh?
NgRx Store lives in memory, so refreshing the page clears it. To persist state, save selected store slices to localStorage (or sessionStorage) using a Meta Reducer or libraries like ngrx-store-localstorage, then restore them when the application starts.
StoreModule.forRoot(reducers, {
metaReducers: [localStorageSyncReducer]
});
What are Meta Reducers in NgRx?
Meta Reducers are higher-order reducers that wrap normal reducers. They execute before or after every reducer call and are commonly used for logging, state persistence, resetting state, or debugging.
export function logger(reducer: ActionReducer<any>): ActionReducer<any> {
return (state, action) => {
console.log(action.type);
return reducer(state, action);
};
}
What are Runtime Checks in NgRx?
Runtime Checks help detect common mistakes such as mutating state, mutating actions, or storing non-serializable objects. They improve application reliability during development by throwing errors when NgRx best practices are violated.
StoreModule.forRoot(reducers, {
runtimeChecks: {
strictStateImmutability: true,
strictActionImmutability: true,
strictStateSerializability: true,
strictActionSerializability: true
}
});
What is the Facade Pattern in NgRx and why is it used?
A Facade is an Angular service that hides NgRx implementation details from components. Components interact only with the Facade instead of directly dispatching actions or selecting state. This reduces coupling, simplifies testing, and makes it easier to replace NgRx later if needed.
@Injectable({ providedIn: 'root' })
export class UserFacade {
users$ = this.store.select(selectUsers);
constructor(private store: Store) {}
loadUsers() {
this.store.dispatch(loadUsers());
}
}
Should Angular components access the Store directly?
Yes, but only for simple applications. In medium or large projects, components should communicate through a Facade Service instead of directly using the Store. This reduces coupling, improves testability, and makes it easier to change the state management implementation later.
export class UserComponent {
users$ = this.userFacade.users$;
constructor(private userFacade: UserFacade) {}
ngOnInit() {
this.userFacade.loadUsers();
}
}
Should a component dispatch multiple NgRx actions?
Generally no. Components should dispatch a single business action such as loadUsers(). If multiple actions are needed, they should be dispatched from an Effect. This keeps components simple and business logic centralized.
// Component
this.store.dispatch(loadUsers());
// Effect
loadUsers$ = createEffect(() =>
this.actions$.pipe(
ofType(loadUsers),
switchMap(() => this.api.getUsers().pipe(
map(users => loadUsersSuccess({ users }))
))
)
);
What is the difference between Smart and Dumb Components in NgRx?
Smart Components interact with the Store (or Facade), dispatch actions, and manage business logic. Dumb Components receive data through @Input() and emit events using @Output(). They contain little or no business logic and are highly reusable.
// Smart Component
<app-user-list
[users]="users$ | async"
(delete)="deleteUser($event)">
</app-user-list>
When should you use NgRx Store instead of Component State?
Use Component State for local UI state like dialog visibility, selected tab, or input values. Use the NgRx Store for shared, long-lived application state such as logged-in user, products, orders, or shopping cart data.
// Component State
isDialogOpen = false;
selectedTab = 1;
// Store State
users$ = this.store.select(selectUsers);
How do multiple Reducers work in an NgRx application?
Each feature has its own reducer responsible for managing only its own slice of state. NgRx combines all reducers into a single Root Store. This keeps features independent, modular, and easier to maintain.
StoreModule.forRoot({
auth: authReducer,
users: userReducer,
products: productReducer,
orders: orderReducer
});
What is the difference between Optimistic Updates and Pessimistic Updates in NgRx?
Optimistic Update updates the UI immediately before the API responds, assuming success. If the API fails, the state is rolled back. Pessimistic Update waits for the API response before updating the Store, ensuring consistency but with a slower UI experience.
// Optimistic
this.store.dispatch(updateUser({ user }));
// API call happens in Effect
// Rollback on failure
// Pessimistic
// Wait for API success
// Then dispatch updateSuccess()
What is @ngrx/router-store and why is it used?
@ngrx/router-store synchronizes Angular Router state with the NgRx Store. It allows selectors, reducers, and effects to access the current route, query parameters, and navigation events without directly injecting the Router.
StoreRouterConnectingModule.forRoot();
this.store.select(selectRouteParams);
What CRUD methods does the NgRx Entity Adapter provide?
The Entity Adapter provides helper methods like addOne(), addMany(), setAll(), updateOne(), upsertOne(), removeOne(), and removeMany(). These methods perform immutable updates automatically.
export const reducer = createReducer(
initialState,
on(loadUsersSuccess, (state, { users }) =>
adapter.setAll(users, state)
),
on(addUserSuccess, (state, { user }) =>
adapter.addOne(user, state)
)
);
What is the difference between withLatestFrom() and combineLatest() inside NgRx Effects?
withLatestFrom() emits only when the source Observable emits, combining it with the latest values from other Observables. combineLatest() emits whenever any input Observable emits after all have emitted once. In Effects, withLatestFrom() is usually preferred to combine an Action with Store data.
loadUsers$ = createEffect(() =>
this.actions$.pipe(
ofType(loadUsers),
withLatestFrom(this.store.select(selectFilters)),
switchMap(([action, filters]) =>
this.api.getUsers(filters)
)
)
);
What is dispatch: false in NgRx Effects and when should you use it?
By default, every Effect dispatches a new Action. Setting dispatch: false tells NgRx that the Effect performs only a side effect (such as navigation, logging, or showing a toast) and should not dispatch another Action.
navigateAfterLogin$ = createEffect(() =>
this.actions$.pipe(
ofType(loginSuccess),
tap(() => this.router.navigate(['/dashboard']))
),
{ dispatch: false }
);