JavaScript performance directly impacts user experience. A slow JavaScript bundle means slow initial page loads. Inefficient runtime code means janky interactions. Unoptimized memory usage causes battery drain on mobile devices. Every millisecond matters.
Measuring Performance
You can't optimize what you don't measure. Chrome DevTools provides excellent profiling tools.
Open DevTools, go to the Performance tab, record a user interaction, and analyze the timeline. Look for long tasks (JavaScript execution taking more than 50ms). These block user interactions and cause jank.
Use the Lighthouse tool (also in DevTools) to measure Core Web Vitals:
- Largest Contentful Paint (LCP): How long until the page's main content loads
- First Input Delay (FID): How long the browser responds to user interaction
- Cumulative Layout Shift (CLS): Visual stability as the page loads
Target: LCP under 2.5 seconds, FID under 100ms, CLS under 0.1.
Code Splitting
Shipping all your JavaScript upfront is wasteful. Users on the homepage don't need the checkout logic.
Modern bundlers like Vite and Webpack support automatic code splitting:
// Load the checkout module only when needed
import("./checkout.js").then(module => {
module.initCheckout();
});React offers suspense boundaries for automatic splitting:
const CheckoutModule = React.lazy(() => import("./Checkout"));
export default function App() {
return (
}>
);
}Every route should load its own bundle. Use dynamic imports to load features only when users access them.
Bundle Analysis
Understand what's in your bundle. Use bundle analyzers to visualize your JavaScript:
npm install -D webpack-bundle-analyzerCheck for duplicate dependencies. A package installed twice wastes kilobytes. Look for large dependencies. Some npm packages are bigger than you'd expect.
Consider alternatives. Is lodash really necessary? Native JavaScript provides map, filter, reduce. Is moment.js needed, or could you use date-fns (8x smaller)?
Tree Shaking
Modern bundlers remove unused code automatically, but only if you use ES modules:
// Good: bundler can tree-shake
import { debounce } from "lodash-es";
// Bad: entire lodash library is included
import _ from "lodash";Always prefer named imports from packages that support tree shaking.
Minification and Compression
Ensure your build process minifies JavaScript. Minification removes whitespace, shortens variable names, and reduces file size by 30-40%.
Enable gzip or brotli compression on your server. Brotli achieves 15-20% better compression than gzip.
Most bundlers handle this automatically. Verify that production builds are minified:
// Production: 12kb minified + gzipped
// Development: 45kb unminifiedRuntime Performance
Optimization doesn't end at bundle size. Runtime performance matters equally.
Avoid Layout Thrashing
Interleaving DOM reads and writes causes forced reflows:
// Bad: causes layout thrashing
elements.forEach(el => {
el.style.width = el.offsetWidth * 2 + "px"; // Read, then write
});Read all values first, then write:
// Good: batches reads and writes
const widths = elements.map(el => el.offsetWidth);
elements.forEach((el, i) => {
el.style.width = widths[i] * 2 + "px";
});Debounce and Throttle
Resize and scroll events fire hundreds of times per second. Don't run expensive logic on every event:
function debounce(fn, wait) {
let timeoutId;
return function(...args) {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => fn(...args), wait);
};
}
window.addEventListener(
"resize",
debounce(() => {
recalculateLayout();
}, 200)
);Use requestAnimationFrame
For animations and visual updates, use requestAnimationFrame instead of setTimeout:
// Good: syncs with browser refresh rate
function animate() {
updatePosition();
requestAnimationFrame(animate);
}
animate();
// Bad: arbitrary timing
setInterval(() => {
updatePosition();
}, 16);Memory Leaks
Unreleased references keep objects in memory indefinitely. Common culprits:
- Event listeners not cleaned up
- Timers not cleared
- Circular references in cached objects
- Large closures in callbacks
Always clean up:
class Component {
constructor(element) {
this.element = element;
this.handleClick = this.onClick.bind(this);
this.element.addEventListener("click", this.handleClick);
}
destroy() {
// Remove listener to prevent memory leak
this.element.removeEventListener("click", this.handleClick);
}
}Virtual Scrolling
Rendering 10,000 list items kills performance. Virtual scrolling renders only visible items:
// Use a library like react-window or vue-virtual-scroller
import { FixedSizeList } from "react-window";
function App() {
return (
{({ index, style }) => (
Item {index}
)}
);
}Web Workers
Offload CPU-intensive work to background threads:
// main.js
const worker = new Worker("worker.js");
worker.postMessage({ data: largeArray });
worker.onmessage = (event) => {
console.log("Result:", event.data);
};
// worker.js
self.onmessage = (event) => {
const result = expensiveCalculation(event.data);
self.postMessage(result);
};Profiling Tools
Beyond DevTools, use these tools:
- WebPageTest: Detailed performance reports from real devices
- Lighthouse CI: Automated performance testing in CI/CD
- Bundle Buddy: Visualize bundle sizes across versions
- pako: Memory usage profiling
Real-World Example
Here's a checklist for optimizing a typical React application:
- Measure baseline performance with Lighthouse
- Analyze bundle size, remove unused packages
- Implement code splitting on routes
- Profile runtime with DevTools Performance tab
- Fix layout thrashing and forced reflows
- Debounce resize/scroll handlers
- Implement virtual scrolling for long lists
- Optimize images (next/image or similar)
- Re-measure and celebrate the wins
Performance is not a one-time optimization. It's an ongoing practice. Build measurement into your workflow, prioritize user experience, and optimize incrementally.
🛠 Essential Reading
Books on JavaScript performance. Affiliate links support this site.
You Don't Know JS Yet (Book Series)
Kyle Simpson's deep dive into JS internals. Understanding the engine helps you write faster code.
High Performance Browser Networking
Essential for understanding why network performance matters as much as code optimization.