React — Performance & Large Datasets
This document contains the complete conversation from this topic, formatted for use in an Astro project. Mermaid diagrams are included where they improve architectural or process understanding.
1. How do you make parallel API calls in React?
Section titled “1. How do you make parallel API calls in React?”Expected discussion
Section titled “Expected discussion”- Promise.all
- Promise.allSettled
- Concurrent requests
- Request batching
Answer
Section titled “Answer”I use Promise.all() when multiple APIs are independent and I need all responses:
const [users, orders, products] = await Promise.all([ fetch("/api/users"), fetch("/api/orders"), fetch("/api/products")]);Key concepts
Section titled “Key concepts”Promise.all→ concurrent requests; fails fast if any request rejects.Promise.allSettled→ waits for all requests and gives success/failure for each.- Concurrent requests → useful when APIs are independent; avoid unnecessary sequential calls.
- Request batching → combine multiple requests into one API call when the backend supports it, reducing network overhead.
Parallel request flow
Section titled “Parallel request flow”flowchart LR
R["React Component"] --> A["API 1"]
R --> B["API 2"]
R --> C["API 3"]
A --> P["Promise.all"]
B --> P
C --> P
P --> UI["Update UI"]
Follow-up: What happens if one API fails inside Promise.all?
Section titled “Follow-up: What happens if one API fails inside Promise.all?”Promise.all() rejects immediately when any promise fails, so you don’t get the successful results through the returned promise.
try { const results = await Promise.all([ api1(), api2(), api3() ]);} catch (error) { // One failure causes Promise.all to reject}If I want partial results, I use Promise.allSettled():
const results = await Promise.allSettled([ api1(), api2(), api3()]);This lets me process successful APIs while handling failed ones independently.
Promise.all vs Promise.allSettled
Section titled “Promise.all vs Promise.allSettled”flowchart TD
A["Multiple API Requests"] --> B{"Which behavior is needed?"}
B -->|"All must succeed"| C["Promise.all"]
B -->|"Partial success is acceptable"| D["Promise.allSettled"]
C --> E["Reject on first rejection"]
D --> F["Wait for every promise"]
F --> G["Handle success and failure individually"]
2. How would you improve page load performance?
Section titled “2. How would you improve page load performance?”Topics to discuss
Section titled “Topics to discuss”- Lazy Loading
- Code Splitting
- Tree Shaking
- CDN
- Compression
- Browser Caching
Answer
Section titled “Answer”I would optimize it at multiple levels:
- Lazy Loading – Load components, images, and routes only when needed.
- Code Splitting – Split large JavaScript bundles using dynamic imports so the initial bundle is smaller.
- Tree Shaking – Remove unused JavaScript/CSS code during the build.
- CDN – Serve static assets from edge locations closer to users.
- Compression – Enable Brotli/Gzip for JS, CSS, HTML, and API responses.
- Browser Caching – Cache static assets using proper
Cache-Controlheaders and hashed filenames.
React example
Section titled “React example”const Dashboard = lazy(() => import("./Dashboard"));Performance optimization flow
Section titled “Performance optimization flow”flowchart LR
A["User"] --> B["Browser"]
B --> C["CDN"]
C --> D["Compressed Assets"]
D --> E["Cached Assets"]
B --> F["Lazy Loaded Components"]
F --> G["Code-Split Bundles"]
G --> H["Smaller Initial Load"]
H --> I["Faster Page Load"]
Interview answer
Section titled “Interview answer”“I would first identify the bottleneck using Lighthouse and Chrome DevTools. Then I would reduce the initial JavaScript using code splitting and lazy loading, remove unused code through tree shaking, optimize static assets through a CDN and compression, and configure browser caching. I would monitor metrics such as LCP, FCP, and INP to validate the improvements.”
3. Explain frontend caching strategies.
Section titled “3. Explain frontend caching strategies.”Answer
Section titled “Answer”I would use caching at multiple levels:
- Browser Cache – Cache static JS, CSS, and images using
Cache-Controland hashed filenames. - HTTP/API Cache – Cache GET API responses using HTTP cache headers like
ETagandCache-Control. - In-memory Cache – Keep frequently used data in React state, Context, or libraries like TanStack Query.
- LocalStorage/SessionStorage – Store small, non-sensitive data that should survive page refreshes.
- Service Worker / PWA Cache – Cache assets and selected API responses for offline or faster loading.
- CDN Cache – Cache static assets and sometimes API responses closer to users.
Caching layers
Section titled “Caching layers”flowchart TD
U["User"] --> B["Browser"]
B --> BC["Browser Cache"]
B --> SW["Service Worker Cache"]
B --> API["API Request"]
API --> HTTP["HTTP/API Cache"]
HTTP --> CDN["CDN Cache"]
CDN --> SERVER["Backend"]
B --> MEM["In-Memory / Query Cache"]
Example cache-control strategy
Section titled “Example cache-control strategy”Static assets can use long-lived caching when filenames contain content hashes:
Cache-Control: public, max-age=31536000, immutableAPI responses may use shorter caching periods depending on how frequently the data changes.
Interview point
Section titled “Interview point”“I choose the caching strategy based on data freshness and sensitivity. Static assets can have long TTLs, while frequently changing API data should have a short TTL or use stale-while-revalidate. For server state, a library such as TanStack Query can simplify cache management.”
4. How would you reduce bundle size for a React application?
Section titled “4. How would you reduce bundle size for a React application?”Answer
Section titled “Answer”I would focus on:
- Code Splitting – Split bundles using
React.lazy()and dynamicimport(). - Tree Shaking – Remove unused code by using ES modules and production builds.
- Lazy Loading – Load heavy components, routes, and libraries only when required.
- Dependency Optimization – Remove unnecessary packages and replace large libraries with lighter alternatives.
- Bundle Analysis – Use tools such as
webpack-bundle-analyzeror Vite’s bundle analysis to identify large dependencies. - Asset Optimization – Compress images and use WebP/AVIF; optimize fonts.
- Minification & Compression – Minify JS/CSS and enable Brotli/Gzip.
- Avoid duplicate dependencies – Check for multiple versions of the same library.
Bundle optimization flow
Section titled “Bundle optimization flow”flowchart TD
A["React Application"] --> B["Analyze Bundle"]
B --> C["Large Dependencies"]
B --> D["Unused Code"]
B --> E["Large Assets"]
B --> F["Duplicate Dependencies"]
C --> G["Replace / Remove"]
D --> H["Tree Shaking"]
E --> I["Optimize Images / Fonts"]
F --> J["Deduplicate Dependencies"]
G --> K["Smaller Bundle"]
H --> K
I --> K
J --> K
K --> L["Faster Initial Load"]
Interview answer
Section titled “Interview answer”“First, I analyze the bundle to identify the largest dependencies. Then I apply code splitting and lazy loading, remove unused dependencies through tree shaking, optimize assets, and enable compression. I also monitor bundle size in CI/CD so it doesn’t regress.”
5. How would you handle large datasets (100k+ records) in the UI?
Section titled “5. How would you handle large datasets (100k+ records) in the UI?”Expected discussion
Section titled “Expected discussion”- Pagination
- Virtualization
- Infinite Scrolling
Answer
Section titled “Answer”I wouldn’t load all 100k records into the browser. I’d handle it at both backend and frontend levels:
- Pagination – Fetch only a limited number of records per request, e.g. 50–100.
- Virtualization – For large lists already loaded in memory, render only visible rows using libraries such as
react-window. - Infinite Scrolling – Fetch the next batch as the user scrolls, useful for feeds/search results.
- Server-side filtering/sorting – Push expensive operations to the backend/database.
- Debouncing – Debounce search/filter requests to avoid excessive API calls.
5.1 Pagination
Section titled “5.1 Pagination”Pagination fetches a limited number of records per request.
For example:
Page 1 → 50 recordsPage 2 → 50 recordsPage 3 → 50 records...React example
Section titled “React example”const fetchUsers = async (page) => { const response = await fetch( `/api/users?page=${page}&size=50` );
return response.json();};Advantages
Section titled “Advantages”- Low network usage
- Lower browser memory consumption
- Simple implementation
- Good for tables and admin screens
- Easy to support page navigation
Best suited for
Section titled “Best suited for”- Data tables
- Search results
- Admin dashboards
- Reports
5.2 Virtualization
Section titled “5.2 Virtualization”Virtualization renders only the rows currently visible in the viewport instead of creating DOM nodes for every record.
For example, even if thousands of records are loaded, the browser may render only the visible 20–50 rows.
flowchart TD
A["Large Dataset"] --> B["Virtualized List"]
B --> C["Visible Rows"]
B --> D["Rows Outside Viewport"]
C --> E["Rendered in DOM"]
D --> F["Not Rendered"]
Concept
Section titled “Concept”100,000 records | vVirtualized List | +----> Visible rows → Render | +----> Off-screen rows → Do not renderReact example
Section titled “React example”Libraries such as react-window can be used:
import { FixedSizeList } from "react-window";
<FixedSizeList height={600} itemCount={users.length} itemSize={50} width="100%"> {Row}</FixedSizeList>Advantages
Section titled “Advantages”- Dramatically reduces DOM nodes
- Improves scrolling performance
- Reduces rendering cost
- Useful for large lists and grids
Best suited for
Section titled “Best suited for”- Large tables
- Logs
- File lists
- Search results
- Chat/message lists
5.3 Infinite Scrolling
Section titled “5.3 Infinite Scrolling”Infinite scrolling loads the next batch when the user approaches the bottom of the list.
sequenceDiagram
participant U as User
participant R as React
participant A as API
participant D as Database
U->>R: Scrolls near bottom
R->>A: Request next batch
A->>D: Fetch records
D-->>A: Return records
A-->>R: Return next batch
R-->>U: Append records
React concept
Section titled “React concept”const loadMore = async () => { const nextPage = page + 1;
const response = await fetch( `/api/users?page=${nextPage}&size=50` );
const data = await response.json();
setUsers((current) => [...current, ...data]); setPage(nextPage);};In production, an IntersectionObserver is often preferable to manually listening for scroll events.
Advantages
Section titled “Advantages”- Good user experience
- No explicit page navigation
- Loads data progressively
- Suitable for feed-like experiences
Best suited for
Section titled “Best suited for”- Social feeds
- Activity feeds
- Product feeds
- Search results
- Content streams
5.4 Server-Side Filtering and Sorting
Section titled “5.4 Server-Side Filtering and Sorting”For very large datasets, filtering and sorting should generally happen on the server.
Instead of:
100,000 records ↓Browser ↓Filter / SortPrefer:
User filter ↓React ↓Backend API ↓Database ↓Small result set ↓Reactflowchart LR
U["User"] --> R["React"]
R --> API["Backend API"]
API --> DB["Database"]
DB --> API
API --> R
Example
Section titled “Example”GET /api/users?search=John&page=0&size=50&sort=nameThis avoids transferring unnecessary records to the browser.
5.5 Debouncing Search
Section titled “5.5 Debouncing Search”When users type into a search field, avoid making an API request for every keystroke.
Without debouncing:
J → API callJo → API callJoh → API callJohn → API callWith debouncing:
John ↓Wait 300–500ms ↓API callThis reduces unnecessary network traffic and backend load.
5.6 Pagination vs Virtualization vs Infinite Scrolling
Section titled “5.6 Pagination vs Virtualization vs Infinite Scrolling”| Strategy | Network Efficiency | DOM Performance | UX | Best Use Case |
|---|---|---|---|---|
| Pagination | Excellent | Excellent | Good | Tables / Admin |
| Virtualization | Depends on data loading | Excellent | Excellent | Large lists |
| Infinite Scrolling | Good | Good | Excellent | Feeds |
| Server-side Filtering | Excellent | Excellent | Excellent | Search / Reports |
These techniques can also be combined.
For example:
Server-side pagination +Virtualized table +Server-side filtering/sortingThis is a strong approach for enterprise applications.
5.7 Recommended Approach for 100k+ Records
Section titled “5.7 Recommended Approach for 100k+ Records”flowchart TD
A["100k+ Records"] --> B{"What type of UI?"}
B -->|"Table / Report"| C["Server-side Pagination"]
B -->|"Large List"| D["Pagination + Virtualization"]
B -->|"Feed / Stream"| E["Infinite Scrolling"]
C --> F["Server-side Filtering & Sorting"]
D --> F
E --> F
F --> G["Debounced Search"]
G --> H["Small API Responses"]
H --> I["Fast React UI"]
Enterprise recommendation
Section titled “Enterprise recommendation”For a typical enterprise React application, I would prefer:
- Server-side pagination
- Server-side filtering
- Server-side sorting
- Virtualization for large rendered lists
- Infinite scrolling only when the UX requires it
- Debounced search
- Cursor-based pagination where appropriate
6. Final Interview Summary
Section titled “6. Final Interview Summary”A concise senior-level answer for this entire performance topic:
“For frontend performance, I first measure the actual bottleneck using Lighthouse and Chrome DevTools. For page load, I use code splitting, lazy loading, tree shaking, compression, CDN delivery, and browser caching. For API calls, I run independent requests concurrently with
Promise.alland usePromise.allSettledwhen partial failures are acceptable. For large datasets, I avoid loading everything into the browser and use server-side pagination, filtering, and sorting. I combine pagination with virtualization for large tables or lists and use infinite scrolling for feed-based experiences. I also debounce search requests and monitor performance metrics and bundle size continuously.”
Quick Revision Cheat Sheet
Section titled “Quick Revision Cheat Sheet”mindmap
root((React Performance))
API Calls
Promise.all
Promise.allSettled
Concurrent Requests
Request Batching
Page Load
Lazy Loading
Code Splitting
Tree Shaking
CDN
Compression
Browser Cache
Frontend Caching
HTTP Cache
In-Memory Cache
LocalStorage
Service Worker
CDN
Bundle Size
Analyze Bundle
Remove Dependencies
Tree Shaking
Dynamic Imports
Asset Optimization
Large Datasets
Pagination
Virtualization
Infinite Scrolling
Server-side Filtering
Server-side Sorting
Debouncing
Key Interview Rules
Section titled “Key Interview Rules”| Problem | Preferred Approach |
|---|---|
| Independent API calls | Promise.all |
| Partial API failures acceptable | Promise.allSettled |
| Slow initial page | Lazy loading + code splitting |
| Unused JS | Tree shaking |
| Static asset delivery | CDN + browser caching |
| Large JS/CSS/assets | Compression + optimization |
| Repeated API data | HTTP/query caching |
| 100k+ table records | Server-side pagination |
| Large rendered list | Virtualization |
| Feed-like UI | Infinite scrolling |
| Expensive search | Debounce + server-side filtering |
| Large dataset sorting | Server-side sorting |
Interview Closing Statement
Section titled “Interview Closing Statement”“My overall approach is to minimize what the browser downloads, minimize what it renders, and avoid unnecessary network calls. I use concurrent API calls where appropriate, cache reusable data, split and lazy-load bundles, and for large datasets I keep the heavy work on the server while using pagination or virtualization on the client.”