React Senior Interview Preparation
This document consolidates the React interview discussion into an Astro-friendly Markdown file.
Topics Covered
Section titled “Topics Covered”- React Rendering Lifecycle
- React.memo, useMemo, and useCallback
- React State Management
- Preventing Unnecessary Re-renders
- Diagnosing Stale Data After API Updates
1. React Rendering Lifecycle
Section titled “1. React Rendering Lifecycle”React rendering can be understood as:
State / Props / Context change ↓ Component render ↓ New React element tree ↓ Reconciliation ↓ Commit DOM updates ↓ Browser paints1.1 Component Render
Section titled “1.1 Component Render”When a React component renders, React executes the component function and produces a new React element tree.
function User({ name }) { return <h1>Hello {name}</h1>;}Conceptually:
User("John") ↓<h1>Hello John</h1>Important Interview Point
Section titled “Important Interview Point”Rendering does not necessarily mean that React updates the real DOM.
React can render a new element tree, compare it with the previous tree, and determine that no DOM change is required.
1.2 Re-render
Section titled “1.2 Re-render”A component may re-render when:
- Its state changes
- Its props change
- Its parent renders
- A consumed Context value changes
- An external store subscription changes
Example:
function Counter() { const [count, setCount] = useState(0);
return ( <> <p>{count}</p>
<button onClick={() => setCount(count + 1)}> Increment </button> </> );}When setCount() runs:
setCount() ↓React schedules an update ↓Counter() executes again ↓New React element tree ↓Reconciliation ↓Required DOM changesA re-render does not mean the entire DOM is recreated.
2. Virtual DOM
Section titled “2. Virtual DOM”The Virtual DOM is commonly described as React’s in-memory representation of the UI.
For example:
<div> <h1>Hello</h1> <p>Welcome</p></div>React creates an internal representation of this UI.
When state changes:
Previous React tree ↓New React tree ↓Compare during reconciliation ↓Determine required changes ↓Commit DOM updatesInterview Point
Section titled “Interview Point”Avoid saying:
“The Virtual DOM makes React automatically faster than every other framework.”
A better explanation is:
React’s declarative rendering model and reconciliation process allow React to determine the necessary UI updates rather than requiring developers to manually manipulate the DOM.
3. Reconciliation
Section titled “3. Reconciliation”Reconciliation is React’s process of comparing the previous rendered tree with the new rendered tree to determine what needs to change.
For example:
Before
Section titled “Before”<h1>Hello John</h1><h1>Hello David</h1>React can identify that:
- The
<h1>still exists. - Only its content changed.
The DOM can therefore be updated accordingly.
3.1 Keys in Lists
Section titled “3.1 Keys in Lists”Keys help React identify list items.
users.map(user => ( <User key={user.id} user={user} />))Keys help React reason about items that are:
- Added
- Removed
- Updated
- Reordered
Stable unique IDs are preferred over array indexes when list items can change order or be inserted/removed.
3.2 Rendering Flow
Section titled “3.2 Rendering Flow”flowchart TD
A["State / Props / Context change"] --> B["Component render"]
B --> C["New React element tree"]
C --> D["Reconciliation"]
D --> E["Determine necessary changes"]
E --> F["Commit DOM updates"]
F --> G["Browser paints"]
4. React.memo
Section titled “4. React.memo”React.memo can prevent a child component from rendering again when its props are unchanged.
const User = React.memo(function User({ name }) { console.log("User rendered");
return <h2>{name}</h2>;});Suppose:
function App() { const [count, setCount] = useState(0);
return ( <> <button onClick={() => setCount(count + 1)}> {count} </button>
<User name="John" /> </> );}When count changes, App renders again.
Without memoization, the child may render again.
With React.memo, React can skip the child when its props remain equal.
App re-renders ↓User props unchanged ↓React.memo ↓User render can be skippedImportant Limitation
Section titled “Important Limitation”React.memo performs a shallow comparison of props by default.
This can cause a problem:
<User user={{ name: "John" }} />A new object is created during every parent render.
Therefore:
previous user object !== new user objectThe memoized component may render again.
5. useMemo
Section titled “5. useMemo”useMemo caches the result of a calculation.
const filteredUsers = useMemo(() => { return users.filter(user => user.name.includes(search) );}, [users, search]);Conceptually:
Component render ↓Have dependencies changed? / \ No Yes ↓ ↓Use cached RecalculatevalueGood Use Cases
Section titled “Good Use Cases”- Expensive filtering
- Expensive sorting
- Complex derived data
- Expensive calculations
- Maintaining stable derived object references when useful
Do not use useMemo for every calculation. Memoization has its own overhead and should solve an actual performance problem.
6. useCallback
Section titled “6. useCallback”useCallback caches a function reference.
Consider:
const User = React.memo(({ onSelect }) => { return ( <button onClick={onSelect}> Select </button> );});Without useCallback:
function App() { const handleSelect = () => { console.log("Selected"); };
return <User onSelect={handleSelect} />;}A new function is created each time App renders.
With useCallback:
const handleSelect = useCallback(() => { console.log("Selected");}, []);The function reference remains stable until its dependencies change.
Parent re-renders ↓useCallback ↓Same function reference ↓React.memo child sees unchanged prop ↓Child can skip render7. React.memo vs useMemo vs useCallback
Section titled “7. React.memo vs useMemo vs useCallback”| Feature | Purpose |
|---|---|
React.memo |
Memoize a component’s rendering based on props |
useMemo |
Memoize a calculated value |
useCallback |
Memoize a function reference |
Memory Trick
Section titled “Memory Trick”React.memo → ComponentuseMemo → ValueuseCallback → Function8. How They Work Together
Section titled “8. How They Work Together”Example:
const UserList = React.memo(({ users, onSelect }) => { return users.map(user => ( <User key={user.id} user={user} onSelect={onSelect} /> ));});
function App({ users }) { const activeUsers = useMemo(() => { return users.filter(user => user.active); }, [users]);
const handleSelect = useCallback((id) => { console.log(id); }, []);
return ( <UserList users={activeUsers} onSelect={handleSelect} /> );}The optimization chain is:
flowchart TD
A["App re-renders"] --> B["useMemo"]
B --> C["Reuse activeUsers if dependencies unchanged"]
A --> D["useCallback"]
D --> E["Reuse handleSelect reference"]
C --> F["UserList receives stable users reference"]
E --> F
F --> G["React.memo"]
G --> H["Skip UserList render when props are unchanged"]
Senior-Level Answer
Section titled “Senior-Level Answer”React rendering starts when state, props, context, or another subscribed source causes an update. React generates a new element tree, reconciles it with the previous tree, and commits the necessary DOM changes.
React.memocan skip a child render when its props are unchanged,useMemocaches expensive computed values, anduseCallbackkeeps function references stable. These should be used selectively after identifying actual performance bottlenecks.
9. State Management in React
Section titled “9. State Management in React”The first step is to classify state.
flowchart TD
A["Application State"] --> B["Client State"]
A --> C["Server State"]
B --> D["Local State"]
B --> E["Shared State"]
D --> F["useState / useReducer"]
E --> G["Context API"]
E --> H["Redux Toolkit"]
E --> I["Zustand"]
C --> J["TanStack Query"]
10. Local State
Section titled “10. Local State”Use useState or useReducer when state belongs to a component or a small subtree.
const [isOpen, setIsOpen] = useState(false);const [name, setName] = useState("");Good Use Cases
Section titled “Good Use Cases”- Modal open/close
- Form input
- Selected tab
- Dropdown state
- Temporary UI state
- Component-specific pagination
When to Use
Section titled “When to Use”Use local state when only one component or a small part of the UI needs the data.
Avoid putting every UI value into global state.
11. Context API
Section titled “11. Context API”Context allows data to be shared without passing props through every intermediate component.
const ThemeContext = createContext();
<ThemeContext.Provider value={theme}> <App /></ThemeContext.Provider>Consume it:
const theme = useContext(ThemeContext);Good Use Cases
Section titled “Good Use Cases”- Theme
- Locale/language
- Current user information
- Application configuration
- Feature flags
Limitation
Section titled “Limitation”Context is not automatically a replacement for a complete state-management library.
Frequently changing Context values can cause many consumers to render.
When to Use
Section titled “When to Use”Use Context for relatively simple shared state that does not require complex state transitions or advanced state-management features.
12. Redux
Section titled “12. Redux”Redux provides centralized, predictable client-side state management.
The traditional flow is:
flowchart LR
A["Component"] --> B["dispatch(action)"]
B --> C["Reducer"]
C --> D["Redux Store"]
D --> E["Subscribed Components"]
E --> F["Render"]
Example:
dispatch({ type: "cart/add", payload: product});Traditional reducers can look like:
function cartReducer(state, action) { switch (action.type) { case "cart/add": return { ...state, items: [ ...state.items, action.payload ] };
default: return state; }}Advantages
Section titled “Advantages”- Predictable state transitions
- Centralized state
- Excellent debugging
- Middleware
- Large ecosystem
Disadvantages
Section titled “Disadvantages”Traditional Redux can involve significant boilerplate.
That is one reason Redux Toolkit is preferred for modern Redux applications.
13. Redux Toolkit
Section titled “13. Redux Toolkit”Redux Toolkit (RTK) is the recommended modern approach to Redux.
Example:
const cartSlice = createSlice({ name: "cart",
initialState: { items: [] },
reducers: { addItem(state, action) { state.items.push(action.payload); },
removeItem(state, action) { state.items = state.items.filter( item => item.id !== action.payload ); } }});RTK uses Immer internally to simplify immutable update logic.
RTK Provides
Section titled “RTK Provides”configureStorecreateSlicecreateAsyncThunk- Middleware integration
- Redux DevTools integration
- RTK Query
When to Use
Section titled “When to Use”RTK is a strong choice for large applications with:
- Complex shared client state
- Multiple modules
- Complex state transitions
- Strong debugging requirements
- Multiple developers
- Long-lived enterprise codebases
14. Zustand
Section titled “14. Zustand”Zustand is a lightweight client-side state-management library.
Example:
const useCartStore = create((set) => ({ items: [],
addItem: (item) => set(state => ({ items: [ ...state.items, item ] }))}));Component:
const items = useCartStore( state => state.items);
const addItem = useCartStore( state => state.addItem);Advantages
Section titled “Advantages”- Very little boilerplate
- Simple API
- Easy to learn
- Selective subscriptions
- Lightweight
Good Use Cases
Section titled “Good Use Cases”- Shopping cart
- UI preferences
- Wizard state
- Dashboard filters
- Editor state
- Small/medium applications
When to Use
Section titled “When to Use”Use Zustand when you need shared client-side state but don’t need the structure and ecosystem of Redux Toolkit.
15. TanStack Query / React Query
Section titled “15. TanStack Query / React Query”TanStack Query is primarily a server-state management library.
Server state is data that originates from a backend:
CustomersOrdersProductsTransactionsUser profileExample:
const { data, isLoading, error} = useQuery({ queryKey: ["customers"], queryFn: fetchCustomers});Mutation example:
const mutation = useMutation({ mutationFn: createCustomer,
onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["customers"] }); }});TanStack Query Handles
Section titled “TanStack Query Handles”- Fetching
- Caching
- Loading state
- Error state
- Refetching
- Stale data
- Background updates
- Request deduplication
- Pagination
- Infinite queries
- Mutations
- Cache invalidation
Why Use It Instead of Redux for API Data?
Section titled “Why Use It Instead of Redux for API Data?”Without a dedicated server-state library, developers may end up manually managing:
loadingerrorcachestale datarefetchretryinvalidationpaginationTanStack Query solves these problems directly.
16. State Management Comparison
Section titled “16. State Management Comparison”| Solution | Best For | Example |
|---|---|---|
useState |
Local UI state | Modal, form, tab |
useReducer |
Complex local state | Multi-step form |
| Context API | Simple shared state | Theme, locale |
| Redux | Complex global client state | Large enterprise app |
| Redux Toolkit | Modern Redux applications | Enterprise client state |
| Zustand | Lightweight global client state | Cart, preferences |
| TanStack Query | Server/API state | Customers, orders |
17. Client State vs Server State
Section titled “17. Client State vs Server State”The most important distinction is:
React Application │ ┌─────────────┴─────────────┐ │ │ Client State Server State │ │ ┌──────┼──────┐ │ │ │ │ │ Local Context Redux/Zustand TanStack Query State APIClient State
Section titled “Client State”State primarily owned by the application/UI:
isModalOpenselectedTabthemesidebarOpenshopping cartform stateServer State
Section titled “Server State”Data originating from the backend:
customersordersproductstransactionsuser profileFor server state, TanStack Query is often a better fit than manually managing API data in Redux.
18. Choosing a State Management Approach
Section titled “18. Choosing a State Management Approach”Small React Application
Section titled “Small React Application”useState +Context where necessaryAvoid introducing Redux simply because it is popular.
Medium Application
Section titled “Medium Application”A possible architecture is:
useState +Context +Zustand +TanStack QueryLarge Enterprise Application
Section titled “Large Enterprise Application”A common architecture could be:
Local UI State ↓useState / useReducer
Shared Client State ↓Redux Toolkit
Server State ↓TanStack Query / RTK QueryThe important principle is:
You do not necessarily need one state-management library for every type of state.
19. Interview-Ready State Management Answer
Section titled “19. Interview-Ready State Management Answer”I first classify state into local UI state, shared client state, and server state. For component-specific state, I use useState or useReducer. For simple cross-component state such as theme, locale, or user context, I use Context API. For complex global client state in an enterprise application, I prefer Redux Toolkit because it provides predictable state transitions, strong DevTools support, and less boilerplate than traditional Redux. For lightweight shared state, Zustand is a good alternative. For server state such as API data, caching, loading, retries, refetching, and invalidation, I prefer TanStack Query rather than putting all API state into Redux.
Memory Trick
Section titled “Memory Trick”Local state → useStateSimple shared → ContextComplex global → Redux ToolkitLightweight → ZustandBackend data → TanStack Query20. Preventing Unnecessary Component Re-renders
Section titled “20. Preventing Unnecessary Component Re-renders”The first step is to determine why the component is rendering.
Then apply targeted optimizations.
20.1 React.memo
Section titled “20.1 React.memo”const UserCard = React.memo(({ user }) => { console.log("UserCard rendered");
return ( <div> {user.name} </div> );});If the parent re-renders but the relevant props remain equal, React can skip the child render.
21. useCallback for Function Props
Section titled “21. useCallback for Function Props”This can cause unnecessary renders:
const handleClick = () => { console.log("clicked");};A new function reference is created on every parent render.
Use:
const handleClick = useCallback(() => { console.log("clicked");}, []);This is especially useful when the callback is passed to a memoized child.
flowchart TD
A["Parent re-renders"] --> B["useCallback"]
B --> C["Stable function reference"]
C --> D["React.memo child"]
D --> E["Child render can be skipped"]
22. useMemo for Expensive Calculations
Section titled “22. useMemo for Expensive Calculations”const filteredUsers = useMemo(() => { return users .filter(user => user.active) .sort((a, b) => a.name.localeCompare(b.name) );}, [users]);This avoids repeating an expensive calculation when its dependencies have not changed.
23. Keep State Local
Section titled “23. Keep State Local”Avoid putting every UI state value into global state.
Instead of:
Global State ├── modalOpen ├── selectedTab ├── inputValue ├── theme └── customerskeep component-specific state locally:
const [isOpen, setIsOpen] = useState(false);This reduces the scope of state updates.
24. Avoid Unnecessary Object and Array Creation
Section titled “24. Avoid Unnecessary Object and Array Creation”This can defeat memoization:
<UserCard user={{ name: "John" }} />A new object is created during each parent render.
Stable references can be created when appropriate:
const user = useMemo(() => ({ name: "John"}), []);However, don’t add useMemo blindly. The best solution may simply be to avoid creating the object unnecessarily.
25. Optimize Context Usage
Section titled “25. Optimize Context Usage”A large Context can cause broad re-rendering.
Instead of:
AppContext ├── user ├── theme ├── notifications ├── cart └── application settingsconsider splitting responsibilities:
UserContextThemeContextCartContextNotificationContextThis can reduce the number of consumers affected by an update.
26. Use Selectors
Section titled “26. Use Selectors”Redux:
const user = useSelector( state => state.user);Prefer selecting only what the component needs.
Zustand:
const username = useUserStore( state => state.username);Selective subscriptions can reduce unnecessary rendering.
27. Optimize Large Lists
Section titled “27. Optimize Large Lists”For thousands of records, rendering every item at once is expensive.
Instead of rendering all items:
users.map(user => ( <UserCard user={user} />))use list virtualization when appropriate.
Conceptually:
10,000 records ↓Virtualized list ↓Only visible rows renderedThis is useful for:
- Tables
- Logs
- Search results
- Large dashboards
- Large lists
28. Lazy Load Large Components
Section titled “28. Lazy Load Large Components”Use code splitting for large parts of an application:
const Reports = lazy( () => import("./Reports"));Then:
<Suspense fallback={<Loading />}> <Reports /></Suspense>Lazy loading primarily reduces initial JavaScript and load time rather than directly preventing ordinary re-renders.
29. React Performance Optimization Strategy
Section titled “29. React Performance Optimization Strategy”flowchart TD
A["Performance issue"] --> B["Use React DevTools Profiler"]
B --> C["Identify rendering bottleneck"]
C --> D["Unnecessary child renders"]
C --> E["Expensive calculation"]
C --> F["Large list"]
C --> G["Large initial bundle"]
C --> H["Too much shared state"]
D --> I["React.memo"]
D --> J["useCallback where useful"]
E --> K["useMemo where useful"]
F --> L["Virtualization"]
G --> M["Lazy loading / code splitting"]
H --> N["Local state / selectors / split Context"]
30. Interview-Ready Re-render Answer
Section titled “30. Interview-Ready Re-render Answer”I first identify the reason for the re-render using React DevTools Profiler rather than applying memoization blindly. I use React.memo for components whose props don’t change frequently, useCallback to maintain stable function references when passing callbacks to memoized children, and useMemo for expensive calculations or stable derived values. I keep state as local as possible, avoid unnecessary object and array creation, split large Context providers, and use selectors for Redux or Zustand. For large lists, I use virtualization, and for large applications I use lazy loading and code splitting.
Quick Formula
Section titled “Quick Formula”React.memo → Memoize componentuseCallback → Memoize functionuseMemo → Memoize valueLocal state → Reduce render scopeContext splitting → Reduce consumersSelectors → Subscribe to needed stateVirtualization → Reduce large-list renderingProfiler → Identify bottlenecksSenior-Level Point
Section titled “Senior-Level Point”useMemo and useCallback are optimization tools, not guarantees that React will never render a component.
Use them when they address a real performance problem.
31. Production Issue: Stale Data After API Updates
Section titled “31. Production Issue: Stale Data After API Updates”Suppose the user updates a customer:
User updates customer ↓PUT /customers/123 ↓200 OK ↓GET /customers/123 ↓Old customer dataThe goal is to determine which layer is serving stale data.
32. Troubleshooting Strategy
Section titled “32. Troubleshooting Strategy”Debug from the UI backward toward the database:
flowchart TD
A["User sees stale data"] --> B["Inspect Browser Network request"]
B --> C{"Does GET response contain NEW data?"}
C -->|Yes| D["React state / client cache issue"]
C -->|No| E["Inspect Browser Cache"]
E --> F{"Is browser cache serving OLD response?"}
F -->|Yes| G["Browser Cache"]
F -->|No| H["Inspect CDN"]
H --> I{"Is CDN serving OLD response?"}
I -->|Yes| J["CDN Cache"]
I -->|No| K["Inspect Application/API Cache"]
K --> L{"Is API cache serving OLD value?"}
L -->|Yes| M["API / Redis / Application Cache"]
L -->|No| N["Compare Database Primary and Replica"]
N --> O{"Primary NEW, Replica OLD?"}
O -->|Yes| P["Replication Lag / Read-after-write consistency"]
O -->|No| Q["Inspect API business logic / DB transaction"]
33. Check React State First
Section titled “33. Check React State First”Open Browser DevTools → Network.
If the GET response contains the new data, but the UI shows old data, the problem is probably in client-side state or cache.
Possible causes:
setCustomer(updatedCustomer);not being called correctly, stale derived state, or a client cache not being updated.
TanStack Query Example
Section titled “TanStack Query Example”After mutation:
await updateCustomer(data);
queryClient.invalidateQueries({ queryKey: ["customer", customerId]});Or update the cache directly:
queryClient.setQueryData( ["customer", customerId], updatedCustomer);Diagnosis
Section titled “Diagnosis”Network GET → NEWReact UI → OLD ↓React state / client cache issue34. Check Browser Cache
Section titled “34. Check Browser Cache”Inspect:
Cache-ControlETagLast-ModifiedAgeExpires- Request/response status
- Memory cache / disk cache indicators
Try disabling cache in DevTools and reload.
If:
Normal request → OLDDisable browser cache → NEWbrowser caching is a strong suspect.
For data that should not be stored at all, an API may use:
Cache-Control: no-storeFor data that can be stored but should be revalidated, an appropriate strategy may use:
Cache-Control: no-cacheThe correct directive depends on the freshness and caching requirements.
35. Check CDN Cache
Section titled “35. Check CDN Cache”A common architecture is:
Browser ↓CDN ↓API Gateway ↓BackendInspect CDN-related response headers. Depending on the provider, examples may include:
Age: 120X-Cache: HITCF-Cache-Status: HITThe exact headers vary by CDN.
If possible, compare:
Client → CDN → Originwith a controlled origin request.
If:
CDN response → OLDOrigin → NEWthe CDN cache is likely stale.
Possible Fixes
Section titled “Possible Fixes”- Correct
Cache-Control - Reduce TTL
- Purge/invalidate CDN cache after updates
- Avoid caching mutation-sensitive endpoints
- Correct cache keys
- Version resources where appropriate
36. Check API/Application Cache
Section titled “36. Check API/Application Cache”Backend architecture may contain:
Controller ↓Service ↓Redis / Caffeine / Hazelcast ↓DatabaseExample:
@Cacheable("customers")public Customer getCustomer(Long id) { return repository.findById(id);}If the update occurs but the cache is not invalidated:
GET customer ↓Application cache ↓OLD customerCommon Cause
Section titled “Common Cause”The update succeeds:
updateCustomer(customer);but the cache remains unchanged.
Possible solution:
@CacheEvict( value = "customers", key = "#customer.id")or update the cache after a successful database write.
Also inspect:
- Cache key
- TTL
- Cache hit/miss
- Cache value
- Invalidation events
37. Check Database Read Replicas
Section titled “37. Check Database Read Replicas”Distributed database architecture can create stale reads:
flowchart TD
A["Application"] --> B["Primary DB"]
A --> C["Read Replica"]
B --> D["UPDATE"]
B --> E["Replication"]
E --> C
C --> F["GET"]
If replication is asynchronous:
UPDATE → Primary ↓ replication ↓ Read Replicathere may be a temporary period where the replica still contains the old data.
Diagnosis
Section titled “Diagnosis”Compare:
Primary DB → NEWRead Replica → OLDIf this occurs, investigate:
- Replication lag
- Read routing
- Transaction commit
- Read-after-write requirements
Possible solutions include:
- Route immediate post-write reads to primary
- Improve replication health
- Use stronger consistency where justified
- Implement appropriate read-your-write behavior
- Retry carefully when eventual consistency is acceptable
38. Production Investigation Checklist
Section titled “38. Production Investigation Checklist”Frontend
Section titled “Frontend”Check:
- Network request/response
- React state
- Redux state
- TanStack Query cache
- Browser cache
- Service worker/PWA cache
- Rendering/derived state logic
Check:
- Cache HIT/MISS
- TTL
Age- Cache key
- Purge/invalidation events
Backend
Section titled “Backend”Check:
- Request/correlation ID
- Cache HIT/MISS
- Cache key
- Cache TTL
- Response timestamp
- Whether DB was queried
Database
Section titled “Database”Check:
- Primary vs replica
- Transaction commit
- Replication lag
- Query result
- Read routing
39. Use Correlation IDs
Section titled “39. Use Correlation IDs”In production, trace one request through the whole system:
sequenceDiagram
participant UI as React UI
participant CDN as CDN
participant API as API Service
participant Cache as Redis
participant DB as Database
UI->>CDN: GET /customers/123 + request-id
CDN->>API: Forward request
API->>Cache: Lookup customer
Cache-->>API: Cache MISS / HIT
API->>DB: Query if required
DB-->>API: Customer data
API-->>CDN: Response
CDN-->>UI: Response
Use a request ID such as:
request-id: abc123Then correlate it across:
- Browser logs
- API gateway logs
- Service logs
- Cache metrics
- Database logs
This makes production diagnosis much faster.
40. Stale Data Diagnostic Table
Section titled “40. Stale Data Diagnostic Table”| Observation | Most Likely Cause |
|---|---|
| API response is fresh, UI is old | React state / client cache |
| Disabling browser cache fixes it | Browser cache |
| CDN says HIT and origin has new data | CDN cache |
| API returns old data and Redis has old value | Application/API cache |
| Primary has new data, replica has old data | Database replication lag |
| All layers have new data but UI is old | React/query cache or rendering logic |
41. Senior-Level Interview Answer: Stale Data
Section titled “41. Senior-Level Interview Answer: Stale Data”I would troubleshoot stale data from the outside in, starting with the browser Network tab and tracing the same request through React state, browser cache, CDN, application cache, and finally the database. First, I determine whether the GET API response itself is stale. If the API returns fresh data but the UI is stale, I investigate React state, Redux, or TanStack Query cache. If the API response is stale, I inspect HTTP cache headers and CDN HIT/MISS information, then check application-level caches such as Redis. Finally, I compare the primary database with read replicas to identify replication lag or read-after-write consistency issues. I would use correlation IDs, cache metrics, timestamps, and request/response headers to identify exactly which layer served the stale value.
42. Overall Interview Mental Model
Section titled “42. Overall Interview Mental Model”flowchart TD
A["React Application"] --> B["Rendering"]
A --> C["State Management"]
A --> D["Performance"]
A --> E["Data Freshness"]
B --> B1["Render"]
B --> B2["Virtual DOM / React tree"]
B --> B3["Reconciliation"]
B --> B4["Commit"]
C --> C1["Local State"]
C --> C2["Context"]
C --> C3["Redux Toolkit"]
C --> C4["Zustand"]
C --> C5["TanStack Query"]
D --> D1["React.memo"]
D --> D2["useMemo"]
D --> D3["useCallback"]
D --> D4["Localize State"]
D --> D5["Selectors"]
D --> D6["Virtualization"]
E --> E1["React / Query Cache"]
E --> E2["Browser Cache"]
E --> E3["CDN Cache"]
E --> E4["API Cache"]
E --> E5["DB Replica"]
43. Quick Revision Sheet
Section titled “43. Quick Revision Sheet”Rendering
Section titled “Rendering”State / Props / Context ↓Render ↓New React tree ↓Reconciliation ↓Commit ↓DOMMemoization
Section titled “Memoization”React.memo → ComponentuseMemo → ValueuseCallback → FunctionState Management
Section titled “State Management”Local UI → useState / useReducerSimple shared → ContextComplex global → Redux ToolkitLightweight global → ZustandServer state → TanStack QueryPerformance
Section titled “Performance”Profiler ↓Find bottleneck ↓Choose targeted optimization ↓React.memo / useMemo / useCallback ↓Local state / selectors ↓Virtualization / lazy loadingStale Data
Section titled “Stale Data”UI ↓React/client cache ↓Browser cache ↓CDN ↓API/application cache ↓Database ↓Read replica44. Final Senior Interview Principle
Section titled “44. Final Senior Interview Principle”A strong senior React engineer should avoid saying:
“I use
React.memo,useMemo, anduseCallbackeverywhere for performance.”
A stronger answer is:
“I first identify the source of the problem using profiling and request tracing. I then optimize the specific bottleneck. I keep state close to where it is needed, distinguish client state from server state, use memoization when reference stability or expensive computation makes it valuable, and treat caching as an end-to-end concern across the browser, CDN, application, and database.”