MindForge
MindForge
DashboardHelp & Support
Dashboard
MindForge

Interview Preparation Bootcamp

Course progress

0%
0 of 177 modules complete
Start Here0/1
  • How to Use This Course
Data Structures & Algorithms0/27
  • Arrays and Hashing
  • Two Pointers
  • Sliding Window
  • Sliding Window: Harder Problems
  • Binary Search
  • Stacks
  • Practice Test: Arrays, Pointers, Windows, Search and Stacks
  • Trees: Traversals and Core Patterns
  • Binary Search Trees and Rebuilding a Tree
  • Tries (Prefix Trees)
  • Heaps and Priority Queues
  • Graphs: BFS and DFS
  • Topological Sort and Cycle Detection
  • Union-Find (Disjoint Set Union)
  • Practice Test: Trees, Heaps and Graphs
  • Dynamic Programming Basics
  • Dynamic Programming: Two Dimensions
  • Dynamic Programming: Hard String Matching
  • Backtracking
  • Backtracking: Constraint Satisfaction
  • Greedy Algorithms
  • Interval Problems
  • Bit Manipulation
  • Math and Geometry
  • String Manipulation
  • Pattern Recall and Mock Coding Rounds
  • Practice Test: DP, Backtracking, Greedy and More

System Design

Foundations0/16
  • The System Design Interview Framework
  • Back-of-the-Envelope Estimation
  • Networking and Protocols for Design
  • API Design and Idempotency
  • Load Balancing, Proxies, and Scaling Out
  • Caching and CDNs
  • Choosing a Database: Storage, Indexes and Transactions
  • Replication, Partitioning, and Sharding
  • CAP, PACELC, and Levels of Consistency
  • Messaging, Queues, and Event-Driven Architecture
  • Distributed Transactions and Data Integrity
  • Coordination, Consensus, and Distributed Locks
  • Reliability, Resilience, and Rate Limiting
  • Observability, Security, and Cost
  • System Design Cheat Sheet
  • Practice Test: System Design Foundations
Case Studies0/23
  • Design a URL Shortener
  • Design a Rate Limiter
  • Design a Notification Service
  • Design a Job Queue System
  • Design a Chat App (WhatsApp)
  • Design an Analytics Pipeline
  • Design a Multi-tenant SaaS Architecture
  • Design a Social Media Feed (Twitter/X)
  • Design a Search Autocomplete System
  • Design a File Storage Service (Dropbox)
  • Design Google Docs
  • Design a Code Review System
  • Design Netflix
  • Design YouTube
  • Design Spotify
  • Design Uber/Lyft
  • Design Airbnb
  • Design Ticketmaster (Eventbrite)
  • Design an E-commerce Cart, Checkout, and Catalog
  • Design a Payment System
  • Design a Logging and Monitoring System
  • Mock Design Rounds
  • Practice Test: System Design Case Studies
Low-Level Design0/19
  • The LLD Interview Framework and UML
  • OOP Foundations for Design
  • SOLID Principles
  • Creational Patterns
  • Structural Patterns
  • Behavioral Patterns I — Strategy, Observer, Command, State, Template Method
  • Behavioral Patterns II and Anti-Patterns
  • Concurrency in Low-Level Design
  • Design a Parking Lot
  • Design an Elevator System
  • Design a Movie Ticket Booking System
  • Design an Expense-Sharing App (Splitwise)
  • Design a Vending Machine and an ATM
  • Design a Rate Limiter and a Logging Framework
  • Design a Notification Service and an In-Memory Cache
  • Design Games — Tic-Tac-Toe, Snake and Ladder, Chess
  • Database Design for LLD
  • LLD Cheat Sheet and Recall Drill
  • Practice Test: Low-Level Design

Backend

Python0/5
  • How Python Runs Your Code
  • Functions Deep Dive: Closures, Generators, Context Managers, Scope
  • Classes Deep Dive: Method Types, MRO, Metaclasses
  • Standard Library and Serialization
  • Practice Test: Python
Django0/6
  • Django Request Lifecycle
  • Lab: Django Middleware — Request ID & Response Time
  • Django ORM and Querysets
  • Django's Cache Framework
  • Practice Test: Django
  • Django in Production: Debugging Labs
FastAPI0/5
  • FastAPI, ASGI, and Async
  • FastAPI Dependency Injection
  • FastAPI Background Tasks
  • Lab: FastAPI Async — Concurrent Aggregation
  • Practice Test: FastAPI
Databases (PostgreSQL)0/6
  • Indexing
  • Query Optimization
  • Transactions and Isolation
  • Migrations and Schema Changes
  • Hierarchical Data and Safe Concurrent Updates
  • Practice Test: Databases
Caching, Queues & Distributed Systems0/8
  • Redis Caching
  • Redis Advanced Patterns
  • Celery and Background Workers
  • Kafka and Message Queues
  • Async Pipelines
  • Distributed Systems Basics
  • Distributed Locks
  • Practice Test: Caching, Queues and Distributed Systems
APIs, Security & Deployment0/9
  • REST API Design
  • API Versioning and Error Handling
  • API Security and Authentication
  • WebSockets and Real-Time Systems
  • Docker and Containers
  • File Storage with S3
  • Testing Strategies
  • Mock Backend Round
  • Practice Test: APIs, Security and Deployment
Frontend0/38
  • HTML Document Structure and Semantics
  • CSS Box Model, Flexbox and Grid
  • CSS Architecture and Theming
  • CSS and Rendering Performance
  • Variables, Scope, Hoisting, and Truthiness
  • Functions, this, and Binding
  • Objects and Prototypal Inheritance
  • JavaScript Utility Patterns: Cloning, Caching, and Memory
  • Promises From Scratch
  • The Event Loop and Browser Rendering Pipeline
  • The DOM: Nodes, Events, and Traversal
  • Practice Test: HTML, CSS and JavaScript
  • React Rendering Fundamentals
  • React Hooks Internals
  • React Performance
  • Lab: React — Stateful Counter Component
  • State Management
  • State Machines in React
  • React Custom Hooks and Composition Patterns
  • TypeScript for React
  • React Testing
  • Practice Test: React
  • HTTP and Caching
  • Network Performance and Web Vitals
  • Code Splitting and Bundles
  • Virtualization
  • GraphQL and Data Fetching
  • Web Security
  • WebSockets and Real-time
  • SSR and Next.js
  • Micro-frontends
  • Build Tools and Bundling
  • Accessibility
  • Error Handling
  • Internationalization
  • Animation
  • Rapid Recall and Mock Frontend Rounds
  • Practice Test: Performance, Architecture and Production
Behavioral & Interview Day0/14
  • Resume and Project Walkthroughs
  • The STAR Method and Refining Your Stories
  • Conflict and Difficult Coworkers
  • Failures, Incidents, and Obstacles
  • Leadership, Mentorship, and Ownership
  • Teamwork, Communication, and Feedback
  • Growth and Handling Ambiguity
  • Career Goals, Why This Company, and Negotiation
  • Situational Questions
  • Company Research
  • Mock Behavioral Rounds
  • Interview Day
  • After the Interview
  • Practice Test: Behavioral and Interview Day
State Management
Course contents

Interview Preparation Bootcamp

Course progress

0%
0 of 177 modules complete
Start Here0/1
  • How to Use This Course
Data Structures & Algorithms0/27
  • Arrays and Hashing
  • Two Pointers
  • Sliding Window
  • Sliding Window: Harder Problems
  • Binary Search
  • Stacks
  • Practice Test: Arrays, Pointers, Windows, Search and Stacks
  • Trees: Traversals and Core Patterns
  • Binary Search Trees and Rebuilding a Tree
  • Tries (Prefix Trees)
  • Heaps and Priority Queues
  • Graphs: BFS and DFS
  • Topological Sort and Cycle Detection
  • Union-Find (Disjoint Set Union)
  • Practice Test: Trees, Heaps and Graphs
  • Dynamic Programming Basics
  • Dynamic Programming: Two Dimensions
  • Dynamic Programming: Hard String Matching
  • Backtracking
  • Backtracking: Constraint Satisfaction
  • Greedy Algorithms
  • Interval Problems
  • Bit Manipulation
  • Math and Geometry
  • String Manipulation
  • Pattern Recall and Mock Coding Rounds
  • Practice Test: DP, Backtracking, Greedy and More

System Design

Foundations0/16
  • The System Design Interview Framework
  • Back-of-the-Envelope Estimation
  • Networking and Protocols for Design
  • API Design and Idempotency
  • Load Balancing, Proxies, and Scaling Out
  • Caching and CDNs
  • Choosing a Database: Storage, Indexes and Transactions
  • Replication, Partitioning, and Sharding
  • CAP, PACELC, and Levels of Consistency
  • Messaging, Queues, and Event-Driven Architecture
  • Distributed Transactions and Data Integrity
  • Coordination, Consensus, and Distributed Locks
  • Reliability, Resilience, and Rate Limiting
  • Observability, Security, and Cost
  • System Design Cheat Sheet
  • Practice Test: System Design Foundations
Case Studies0/23
  • Design a URL Shortener
  • Design a Rate Limiter
  • Design a Notification Service
  • Design a Job Queue System
  • Design a Chat App (WhatsApp)
  • Design an Analytics Pipeline
  • Design a Multi-tenant SaaS Architecture
  • Design a Social Media Feed (Twitter/X)
  • Design a Search Autocomplete System
  • Design a File Storage Service (Dropbox)
  • Design Google Docs
  • Design a Code Review System
  • Design Netflix
  • Design YouTube
  • Design Spotify
  • Design Uber/Lyft
  • Design Airbnb
  • Design Ticketmaster (Eventbrite)
  • Design an E-commerce Cart, Checkout, and Catalog
  • Design a Payment System
  • Design a Logging and Monitoring System
  • Mock Design Rounds
  • Practice Test: System Design Case Studies
Low-Level Design0/19
  • The LLD Interview Framework and UML
  • OOP Foundations for Design
  • SOLID Principles
  • Creational Patterns
  • Structural Patterns
  • Behavioral Patterns I — Strategy, Observer, Command, State, Template Method
  • Behavioral Patterns II and Anti-Patterns
  • Concurrency in Low-Level Design
  • Design a Parking Lot
  • Design an Elevator System
  • Design a Movie Ticket Booking System
  • Design an Expense-Sharing App (Splitwise)
  • Design a Vending Machine and an ATM
  • Design a Rate Limiter and a Logging Framework
  • Design a Notification Service and an In-Memory Cache
  • Design Games — Tic-Tac-Toe, Snake and Ladder, Chess
  • Database Design for LLD
  • LLD Cheat Sheet and Recall Drill
  • Practice Test: Low-Level Design

Backend

Python0/5
  • How Python Runs Your Code
  • Functions Deep Dive: Closures, Generators, Context Managers, Scope
  • Classes Deep Dive: Method Types, MRO, Metaclasses
  • Standard Library and Serialization
  • Practice Test: Python
Django0/6
  • Django Request Lifecycle
  • Lab: Django Middleware — Request ID & Response Time
  • Django ORM and Querysets
  • Django's Cache Framework
  • Practice Test: Django
  • Django in Production: Debugging Labs
FastAPI0/5
  • FastAPI, ASGI, and Async
  • FastAPI Dependency Injection
  • FastAPI Background Tasks
  • Lab: FastAPI Async — Concurrent Aggregation
  • Practice Test: FastAPI
Databases (PostgreSQL)0/6
  • Indexing
  • Query Optimization
  • Transactions and Isolation
  • Migrations and Schema Changes
  • Hierarchical Data and Safe Concurrent Updates
  • Practice Test: Databases
Caching, Queues & Distributed Systems0/8
  • Redis Caching
  • Redis Advanced Patterns
  • Celery and Background Workers
  • Kafka and Message Queues
  • Async Pipelines
  • Distributed Systems Basics
  • Distributed Locks
  • Practice Test: Caching, Queues and Distributed Systems
APIs, Security & Deployment0/9
  • REST API Design
  • API Versioning and Error Handling
  • API Security and Authentication
  • WebSockets and Real-Time Systems
  • Docker and Containers
  • File Storage with S3
  • Testing Strategies
  • Mock Backend Round
  • Practice Test: APIs, Security and Deployment
Frontend0/38
  • HTML Document Structure and Semantics
  • CSS Box Model, Flexbox and Grid
  • CSS Architecture and Theming
  • CSS and Rendering Performance
  • Variables, Scope, Hoisting, and Truthiness
  • Functions, this, and Binding
  • Objects and Prototypal Inheritance
  • JavaScript Utility Patterns: Cloning, Caching, and Memory
  • Promises From Scratch
  • The Event Loop and Browser Rendering Pipeline
  • The DOM: Nodes, Events, and Traversal
  • Practice Test: HTML, CSS and JavaScript
  • React Rendering Fundamentals
  • React Hooks Internals
  • React Performance
  • Lab: React — Stateful Counter Component
  • State Management
  • State Machines in React
  • React Custom Hooks and Composition Patterns
  • TypeScript for React
  • React Testing
  • Practice Test: React
  • HTTP and Caching
  • Network Performance and Web Vitals
  • Code Splitting and Bundles
  • Virtualization
  • GraphQL and Data Fetching
  • Web Security
  • WebSockets and Real-time
  • SSR and Next.js
  • Micro-frontends
  • Build Tools and Bundling
  • Accessibility
  • Error Handling
  • Internationalization
  • Animation
  • Rapid Recall and Mock Frontend Rounds
  • Practice Test: Performance, Architecture and Production
Behavioral & Interview Day0/14
  • Resume and Project Walkthroughs
  • The STAR Method and Refining Your Stories
  • Conflict and Difficult Coworkers
  • Failures, Incidents, and Obstacles
  • Leadership, Mentorship, and Ownership
  • Teamwork, Communication, and Feedback
  • Growth and Handling Ambiguity
  • Career Goals, Why This Company, and Negotiation
  • Situational Questions
  • Company Research
  • Mock Behavioral Rounds
  • Interview Day
  • After the Interview
  • Practice Test: Behavioral and Interview Day
Browsing without an account — progress is saved in this browser only.Sign in to save it

State Management

Reading35 min

"When would you reach for Redux versus Context versus local state?" is one of the most common system-design-flavored frontend questions there is, because the honest answer reveals whether you've actually shipped something at scale or just followed a tutorial. This lesson builds a Redux-like store from scratch so "how does Redux work internally" stops being a black box, covers useReducer for local, complex state, and settles the controlled-versus-uncontrolled question for form inputs.

Picking a scope, narrowest first

Default to the smallest scope that works, and only lift when there's a concrete reason to.

Signal Use
Only one component (and maybe its direct children) needs the value useState / useReducer in that component
Several sibling components need it, but it doesn't cross large parts of the tree Lift to their common parent, pass down as props
Truly cross-cutting (theme, auth session, locale) and rarely changes React Context
Complex, frequently-updated, shared across distant parts of the app, needs middleware/devtools/time-travel Redux / Zustand / Jotai
Comes from the server and just needs caching plus revalidation React Query / SWR, not Redux

Context is not a state management solution on its own, it's a dependency-injection mechanism for avoiding prop drilling. It has no built-in way to stop unrelated consumers from re-rendering on unrelated value changes: every consumer of a Context re-renders on any change to that Context's value, unless you split contexts or memoize deliberately, and it has no middleware, devtools, or selectors. Redux and Zustand solve a genuinely different problem: efficient, selective subscriptions to slices of a large, frequently-changing store.

Redux Context API Zustand
Boilerplate High (actions, reducers, dispatch), much less with Redux Toolkit Low Very low
Re-render granularity Fine: useSelector only re-renders on the selected slice Coarse: any value change re-renders every consumer Fine: selector-based
DevTools / time-travel Yes No Via middleware
Async / middleware Thunks, Sagas, RTK Query None built-in Simple built-in async actions
Best for Large apps, complex cross-cutting state Rarely-changing app-wide values Apps that outgrew Context but don't need Redux's ceremony

Remember: Context is dependency injection, not a state manager. Every consumer re-renders on any value change, with no built-in way to subscribe to just a slice.

Sign in to try this knowledge check.

Building a store from scratch

This is the core of what createStore actually does: a subscription list, a reducer, and a dispatch that runs the reducer and notifies every subscriber.

tsx
type Action = { type: string; payload?: unknown };
type Reducer<S> = (state: S, action: Action) => S;

function createStore<S>(reducer: Reducer<S>, initialState: S) {
  let state = initialState;
  const listeners = new Set<() => void>();

  function getState(): S { return state; }
  function dispatch(action: Action): Action {
    state = reducer(state, action); // reducer MUST be pure: same inputs → same output, no mutation
    listeners.forEach(listener => listener());
    return action;
  }
  function subscribe(listener: () => void): () => void {
    listeners.add(listener);
    return () => listeners.delete(listener);
  }
  return { getState, dispatch, subscribe };
}

type CounterState = { count: number };
function counterReducer(state: CounterState, action: Action): CounterState {
  switch (action.type) {
    case 'increment': return { count: state.count + 1 }; // new object — never mutate directly
    case 'decrement': return { count: state.count - 1 };
    default: return state;
  }
}

const store = createStore(counterReducer, { count: 0 });
store.subscribe(() => console.log('new state:', store.getState()));
store.dispatch({ type: 'increment' }); // logs: new state: { count: 1 }

A React binding for this, roughly what react-redux's useSelector does, uses useSyncExternalStore, the hook purpose-built for subscribing to external, non-React state sources without tearing:

tsx
import { useSyncExternalStore } from 'react';

function useStoreSelector<S, T>(store: { getState: () => S; subscribe: (l: () => void) => () => void }, selector: (state: S) => T): T {
  return useSyncExternalStore(store.subscribe, () => selector(store.getState()));
}

function Counter() {
  const count = useStoreSelector(store, s => s.count);
  return <button onClick={() => store.dispatch({ type: 'increment' })}>{count}</button>;
}

A reducer is a pure function with the signature (state, action) => newState, and dispatch(action) is the only sanctioned way to trigger one; action is a plain object with a type field and usually a payload. Never mutate state in place, both React and Redux detect change with a reference-equality check, and a mutated-in-place object still has the exact same reference, so the change goes unnoticed. combineReducers lets each reducer own one slice of the overall state tree, which keeps individual reducers small and independently testable, rather than one giant reducer trying to handle every action for the whole app.

Remember: a store is nothing more than state plus a pure reducer plus a set of subscribers. dispatch runs the reducer, then notifies everyone.

Sign in to try this knowledge check.

useReducer: the same pattern, scaled down to one component

useReducer is createStore shrunk to a single component, same reducer pattern, no external store. Reach for it over a pile of useState calls when fields update together, or when the "next state" logic reads more clearly as a switch statement than as scattered setters.

tsx
type FormState = {
  values: { email: string; password: string };
  errors: { email?: string; password?: string };
  isSubmitting: boolean;
};
type FormAction =
  | { type: 'CHANGE_FIELD'; field: 'email' | 'password'; value: string }
  | { type: 'SET_ERRORS'; errors: FormState['errors'] }
  | { type: 'SUBMIT_START' } | { type: 'SUBMIT_END' };

function formReducer(state: FormState, action: FormAction): FormState {
  switch (action.type) {
    case 'CHANGE_FIELD':
      return {
        ...state,
        values: { ...state.values, [action.field]: action.value },
        errors: { ...state.errors, [action.field]: undefined }, // clear that field's error on edit
      };
    case 'SET_ERRORS': return { ...state, errors: action.errors };
    case 'SUBMIT_START': return { ...state, isSubmitting: true };
    case 'SUBMIT_END': return { ...state, isSubmitting: false };
    default: return state;
  }
}

function LoginForm() {
  const [state, dispatch] = useReducer(formReducer, {
    values: { email: '', password: '' }, errors: {}, isSubmitting: false,
  });
  const handleChange = (field: 'email' | 'password') => (e: React.ChangeEvent<HTMLInputElement>) => {
    dispatch({ type: 'CHANGE_FIELD', field, value: e.target.value });
  };
  return (
    <form>
      <input value={state.values.email} onChange={handleChange('email')} />
      {state.errors.email && <span>{state.errors.email}</span>}
      <button disabled={state.isSubmitting}>Log in</button>
    </form>
  );
}

SET_ERRORS is what a submit handler dispatches after running validation, replacing the whole errors object in one atomic update rather than setting each field's error individually.

The win over five separate useState calls: every field update is one dispatch with clear intent, the reducer is a pure function you can unit-test with zero rendering involved, and related fields update together atomically instead of risking an inconsistent intermediate render where values updated but errors hasn't yet.

Remember: reach for useReducer over several useState calls when fields update together, or the "next state" logic reads better as one switch statement.

Sign in to try this knowledge check.

Controlled vs. uncontrolled inputs

A controlled input has React state as its single source of truth: the input's value comes from state, and every keystroke updates that state through onChange.

tsx
function ControlledInput() {
  const [value, setValue] = useState("");
  return <input value={value} onChange={(e) => setValue(e.target.value)} />;
}

An uncontrolled input lets the DOM hold the value itself. React only reaches in when it actually needs the value, through a ref, instead of tracking every keystroke.

tsx
function UncontrolledInput() {
  const ref = useRef<HTMLInputElement>(null);
  const submit = () => console.log(ref.current?.value);
  return <input ref={ref} defaultValue="" />;
}

The trade-off is exactly what it looks like: controlled gives you validation on every keystroke and state that's always current, at the cost of a re-render on every keystroke. Uncontrolled is cheaper, since typing causes zero React re-renders, but you only get the value when you explicitly ask for it, typically on submit. A large form with dozens of fields and no per-keystroke validation is the classic case for going uncontrolled, which is exactly why React Hook Form keeps inputs uncontrolled internally and only re-renders on submit or blur, worth naming directly as the answer to "how would you make a big form scale."

Remember: controlled means React state drives the value, and re-renders on every keystroke. Uncontrolled means the DOM holds it, and you read it through a ref when you need it.

Sign in to try this knowledge check.

Immutable updates and normalized shape

React and Redux both detect change with reference equality, Object.is/===, not deep equality. Mutating state in place never changes the reference, so nothing downstream ever notices, the UI just silently goes stale.

tsx
// WRONG — mutates, reference stays the same, nothing re-renders
state.user.name = 'New Name';
state.items.push(newItem);

// RIGHT — spread creates new references at every level that actually changed
const newState = {
  ...state,
  user: { ...state.user, name: 'New Name' },
  items: [...state.items, newItem],
};

For deeply nested state, hand-spreading at every level gets unreadable fast, which is exactly the problem Immer (used internally by Redux Toolkit) solves: you write code that looks mutable, and it produces a new immutable tree behind the scenes via a proxy.

Nested, duplicated state, arrays of objects containing arrays of objects, makes a simple update require deep traversal, and risks the same entity existing in two places with two different values.

tsx
// Denormalized — hard to update a single comment, author data duplicated across posts
type DenormalizedState = {
  posts: Array<{
    id: string; title: string;
    author: { id: string; name: string };
    comments: Array<{ id: string; text: string; author: { id: string; name: string } }>;
  }>;
};

Normalize it the way a relational database would: flat lookup tables keyed by ID, plus ID arrays for ordering.

tsx
// Normalized — O(1) lookup and update by ID, single source of truth per entity
type NormalizedState = {
  posts: { byId: Record<string, { id: string; title: string; authorId: string; commentIds: string[] }>; allIds: string[] };
  comments: { byId: Record<string, { id: string; text: string; authorId: string }>; allIds: string[] };
  users: { byId: Record<string, { id: string; name: string }>; allIds: string[] };
};

function editComment(state: NormalizedState, commentId: string, text: string): NormalizedState {
  return {
    ...state,
    comments: { ...state.comments, byId: { ...state.comments.byId, [commentId]: { ...state.comments.byId[commentId], text } } },
  };
}

Editing one comment becomes a targeted, cheap operation, exactly one entry in one lookup table changes, instead of hunting through a nested array of posts to find the right comment buried three levels deep.

Remember: denormalized, duplicated state risks the same entity existing in two places with two different values. Normalize it into flat, keyed lookup tables instead.

Sign in to try this knowledge check.
PreviousLab: React — Stateful Counter ComponentNextState Machines in React

Course progress

0%
0 of 177 modules complete

On this page

  • Picking a scope, narrowest first
  • Building a store from scratch
  • useReducer: the same pattern, scaled down to one component
  • Controlled vs. uncontrolled inputs
  • Immutable updates and normalized shape