Suggest an editImprove this articleRefine the answer for “Ways to optimize applications”. Your changes go to moderation before they’re published.Approval requiredContentWhat you’re changing🇺🇸EN🇺🇦UAPreviewTitle (EN)Short answer (EN)**Ways to optimize applications** span network, loading, and runtime layers. Enable Gzip/Brotli, use a CDN, switch to HTTP/2, split code at route boundaries, lazy-load images with `loading="lazy"`, debounce inputs, and virtualize long lists. ```js // Load Dashboard only when the route is visited const Dashboard = React.lazy(() => import('./Dashboard')); ``` **Key point:** run Lighthouse before changing anything. Memoization and code splitting without profiling add overhead instead of removing it.Shown above the full answer for quick recall.Answer (EN)Image**Ways to optimize applications** - a set of techniques that reduce load time, lower render cost, and make interactions feel fast across network, asset, and runtime layers. ## Theory ### TL;DR - Network: compress with Gzip/Brotli, use a CDN, enable HTTP/2, set `Cache-Control` headers on static assets - Loading: split code at route boundaries, lazy-load images and components, convert images to WebP/AVIF - Runtime: debounce input handlers, offload heavy work to Web Workers, virtualize long lists - React: use `useMemo`, `useCallback`, `React.memo` only after a profiler confirms a real bottleneck - Measure first: Lighthouse, Web Vitals (LCP, FID, CLS), DevTools Performance tab ### Network layer Gzip reduces HTML/CSS/JS transfer size by 60-80% on average. Brotli compresses roughly 15-20% better than Gzip for text assets. Most servers (Nginx, Apache) support both. Enable them for all text-based responses, not just HTML. **CDN** puts static assets (images, fonts, scripts) on servers geographically close to your users. Cloudflare and Amazon CloudFront are the most common choices. A user in Warsaw loading assets from a Frankfurt edge node instead of a US origin server drops round-trip from ~120ms to ~15ms. That gap multiplies across every resource on the page. HTTP/2 changes how the browser and server communicate. HTTP/1.1 queues requests on a single connection. HTTP/2 multiplexes them all at once, so 20 parallel asset requests do not block each other. It also compresses headers with HPACK. The gain is most visible on pages with many small assets. ### Caching headers Set `Cache-Control: max-age=31536000, immutable` on versioned static assets (files with a content hash in the name, like `main.a3f9c2.js`). The browser skips any network request for a full year. For HTML, use `Cache-Control: no-cache` with an `ETag` so the browser validates freshness without re-downloading the file if nothing changed. ### Code splitting and lazy loading Webpack and Vite both split your bundle automatically at dynamic `import()` calls. A React app that ships one 800KB bundle becomes several 50-100KB chunks loaded on demand. The browser downloads only the code needed for the current route. `React.lazy` wraps a dynamic import and `Suspense` shows a fallback while it loads: ```jsx const Dashboard = React.lazy(() => import('./Dashboard')); function App() { return ( <Suspense fallback={<Spinner />}> <Dashboard /> </Suspense> ); } ``` For images, the native `loading="lazy"` attribute on `<img>` defers loading until the element enters the viewport. No JavaScript needed. Add `width` and `height` attributes to prevent layout shift. ### Image optimization Switch to WebP or AVIF. WebP averages 25-35% smaller than JPEG at equivalent quality. AVIF compresses further, though encoder support arrived later. Use `<picture>` to provide fallbacks: ```html <picture> <source srcset="photo.avif" type="image/avif"> <source srcset="photo.webp" type="image/webp"> <img src="photo.jpg" alt="..." loading="lazy" width="800" height="600"> </picture> ``` If you serve images through a CDN with automatic format negotiation (Cloudinary, Imgix), the CDN handles format selection via URL parameters and you skip the `<picture>` markup entirely. ### Debounce and throttle A search input that fires a request on every keystroke can generate 10-20 network calls per second. Debounce delays execution until the user pauses: ```js function debounce(fn, delay) { let timer; return (...args) => { clearTimeout(timer); timer = setTimeout(() => fn(...args), delay); }; } const handleSearch = debounce((query) => fetchResults(query), 300); ``` Throttle works differently. It fires at a fixed interval regardless of call frequency. Use throttle for scroll and resize handlers, where you want consistent updates without skipping entirely. ### Web Workers JavaScript runs on one thread. Heavy computation on the main thread blocks rendering and makes the UI freeze. Web Workers run on a separate thread, communicate via `postMessage`, and have no access to the DOM. ```js // main.js const worker = new Worker('worker.js'); worker.postMessage({ data: largeArray }); worker.onmessage = (e) => console.log(e.data.result); // worker.js self.onmessage = (e) => { const result = processData(e.data.data); // runs off main thread self.postMessage({ result }); }; ``` Good use cases: CSV parsing, image processing, sorting large datasets, encryption. ### Virtualization for long lists Rendering 10,000 `<tr>` rows creates 10,000 DOM nodes. The browser paints and tracks all of them even if only 20 fit on screen. Virtualization renders only the visible rows. Libraries like `react-window` and `@tanstack/virtual` handle the math. A list that took 3 seconds to render with a full DOM can drop to under 100ms with virtualization. ### React memoization `useMemo`, `useCallback`, and `React.memo` are not free. They cost memory and comparison overhead on every render. The mistake is adding them everywhere preemptively. Open React Profiler first to confirm a component is actually re-rendering unnecessarily. Then apply memoization to that specific component. `React.memo` wraps a component and skips re-render when props are shallowly equal. `useMemo` caches an expensive computed value. `useCallback` caches a function reference so child components receiving it as a prop do not re-render unnecessarily. ### Performance measurement Four tools cover most cases: - **Lighthouse** (Chrome DevTools Audits tab): scores LCP, FID/INP, CLS, and gives actionable recommendations - **Web Vitals**: LCP under 2.5s, FID under 100ms, CLS under 0.1 are Google's "good" thresholds - **DevTools Performance tab**: records a trace of rendering, scripting, and painting, useful for finding what runs during a slow interaction - **WebPageTest**: tests from real locations and devices, including simulated 3G connections React Profiler (browser extension or `<Profiler>` API) shows which components render, how often, and how long each takes. ### Common mistakes **Memoizing everything.** Adding `useMemo` and `useCallback` to every function adds overhead with no benefit when the component is cheap to re-render. **Splitting too aggressively.** Too many small chunks means too many HTTP requests. Route-level splitting is almost always the right granularity. **CDN without cache headers.** Files behind a CDN with `Cache-Control: no-store` cause the CDN to fetch from origin on every request. You get geographic distribution but no caching. **Lazy loading above-the-fold images.** The hero image or logo should not have `loading="lazy"`. That delays the LCP score directly. **Skipping measurement.** Over-splitting and unnecessary cache invalidation can make performance worse. Profile before you change anything. ### Follow-up questions **Q:** What is the difference between debounce and throttle? **A:** Debounce waits until a pause in calls, then fires once. Throttle fires at a fixed rate regardless of call frequency. For search inputs, debounce. For scroll handlers, throttle. **Q:** When should you avoid `React.memo`? **A:** When the component is cheap to render, or when props change on nearly every parent render. The shallow comparison cost exceeds the re-render cost in those cases. **Q:** What HTTP headers control browser caching? **A:** `Cache-Control` sets the policy (max-age, no-cache, immutable). `ETag` is a fingerprint the browser sends back to check if content changed. `Last-Modified` does the same with a timestamp. **Q:** Why does HTTP/2 improve performance over HTTP/1.1? **A:** HTTP/1.1 processes one request per connection at a time. HTTP/2 multiplexes many requests over a single TCP connection, eliminating head-of-line blocking for asset loading. **Q:** What are Core Web Vitals and why do they matter for search ranking? **A:** LCP, FID/INP, and CLS measure real user experience: load time for main content, input responsiveness, and visual stability. Google includes them in search ranking signals since 2021. **Q:** What is the difference between virtualization and pagination for large lists? **A:** Pagination loads a subset of data and requires the user to navigate between pages. Virtualization loads all data but renders only the visible rows in the DOM. Virtualization gives a smoother scroll experience; pagination reduces total data transferred. ## Examples ### Code splitting with React.lazy ```jsx import React, { Suspense } from 'react'; // UserProfile is bundled separately, fetched only when isLoggedIn becomes true const UserProfile = React.lazy(() => import('./UserProfile')); function App({ isLoggedIn }) { return ( <div> {isLoggedIn && ( <Suspense fallback={<div>Loading profile...</div>}> <UserProfile /> </Suspense> )} </div> ); } ``` Webpack or Vite emit `UserProfile` as a separate chunk. The browser downloads it only when `isLoggedIn` is true. The initial bundle ships without it, cutting first-load size. ### Debounce for a search input ```js function debounce(fn, delay) { let timerId; return function (...args) { clearTimeout(timerId); timerId = setTimeout(() => fn.apply(this, args), delay); }; } const searchInput = document.querySelector('#search'); const fetchSuggestions = debounce(async (query) => { const res = await fetch(`/api/suggest?q=${query}`); const data = await res.json(); renderSuggestions(data); }, 300); searchInput.addEventListener('input', (e) => { fetchSuggestions(e.target.value); }); ``` Typing "react hooks" (10 characters) without debounce fires 10 requests. With 300ms debounce, it fires one after the user pauses. On a product with 50k daily active users, debouncing search can cut API request volume by 80-90%. ### Virtual list with react-window ```jsx import { FixedSizeList } from 'react-window'; const rows = Array.from({ length: 10000 }, (_, i) => ({ id: i, name: `Item ${i}` })); function Row({ index, style }) { return ( <div style={style}> {rows[index].name} </div> ); } function VirtualList() { return ( <FixedSizeList height={600} itemCount={rows.length} itemSize={40} width="100%" > {Row} </FixedSizeList> ); } ``` `react-window` renders only the rows that fit in the 600px container. At 40px per row that is 15 DOM nodes instead of 10,000. I have seen this pattern cut initial render time on a reporting table from 4 seconds to under 150ms.For the reviewerNote to the moderator (optional)Visible only to the moderator. Helps review go faster.