DEV Community

Cover image for Tree Shaking in JavaScript: How It Works and Why It Matters
MD. JAHID HOSSAIN
MD. JAHID HOSSAIN

Posted on

Tree Shaking in JavaScript: How It Works and Why It Matters

Modern JavaScript applications often rely on dozens of libraries and utilities.
As projects grow, the bundle size can quickly increase - sometimes including code your application never even uses.

This unnecessary code slows down loading times and hurts performance.

Large bundle size is one of the biggest performance problems in modern web applications.

That’s where Tree Shaking comes in.

Tree Shaking is a powerful JavaScript optimization technique used by modern JavaScript bundlers like Webpack, Rollup, Vite and esbuild to automatically remove unused code from your application.

In this article we will explore:

  • What Tree Shaking is
  • Why it matters for performance
  • How it works internally
  • Practical examples
  • Best practices for using it effectively

What is Tree Shaking?

Definition

Tree Shaking is a build optimization technique that removes unused JavaScript code from the final bundle.

It works by analyzing ES module imports and exports and eliminating functions, variables, or modules that are not actually used.

Simple Explanation

Imagine a file exports 10 functions, but your app only uses 2 of them.

Tree Shaking ensures that only those 2 functions remain in the final production bundle.

The other 8 functions are removed automatically during the build process.


Why Is It Called “Tree Shaking”?

The name comes from a visual analogy.

Imagine your codebase as a tree.

  • Files → branches

  • Functions/exports → leaves

  • Imports → connections

When the bundler analyzes your application, it “shakes the tree” and unused leaves fall off.

Conceptual Diagram

            math.js  
   ---------------------------------  
   add | subtract | multiply | divide  
   ---------------------------------  
            |  
            | imported  
            v  
          app.js  
            |  
            v  
     Final Production Bundle  
            |  
            v  
           add()
Enter fullscreen mode Exit fullscreen mode

Unused functions:

subtract()  
multiply()  
divide()
Enter fullscreen mode Exit fullscreen mode

These are removed during the build.


Why Tree Shaking Matters

Without Tree Shaking, your application might ship a lot of unnecessary code to users.

That leads to slower loading times and worse performance.

Benefits

Benefit Explanation
Smaller bundle size Less JavaScript downloaded
Faster page load Reduced network transfer
Better performance Browser parses less code
Improved user experience Faster application startup
Lower bandwidth usage Especially helpful for mobile users

Real-Life Example

Imagine you're moving to a new house.

You open your wardrobe and find:

  • clothes you wear every day

  • clothes you haven’t worn for years

  • broken accessories

  • old shoes

Instead of packing everything, you only pack what you actually use.

Tree Shaking works the same way.

Your project may contain many functions, but the final bundle only includes the ones your application needs.


Basic Example

Let’s look at a simple example.

math.js

export function add(a, b) {  
  return a + b;  
}  

export function subtract(a, b) {  
  return a - b;  
}  

export function multiply(a, b) {  
  return a * b;  
}  

export function divide(a, b) {  
  return a / b;  
}
Enter fullscreen mode Exit fullscreen mode

app.js

import { add } from "./math.js";  

console.log(add(2, 3));
Enter fullscreen mode Exit fullscreen mode

Here the application only uses add().

A tree-shaking-enabled bundler removes:

  • subtract

  • multiply

  • divide

from the final bundle.


How Tree Shaking Works

Tree Shaking typically happens during the build process.

Flow Diagram

Source Code  
     |  
     v  
Bundler analyzes imports/exports  
     |  
     v  
Builds dependency graph  
     |  
     v  
Detects unused exports  
     |  
     v  
Marks removable code  
     |  
     v  
Optimizer removes unused code  
     |  
     v  
Final optimized bundle
Enter fullscreen mode Exit fullscreen mode

Build Optimization Pipeline

Developer Code
      |
      v
Bundler builds dependency graph
      |
      v
Unused exports detected
      |
      v
Tree Shaking removes unused code
      |
      v
Minifier compresses remaining code
      |
      v
Optimized production bundle
Enter fullscreen mode Exit fullscreen mode

Dependency Graph Visualization

When bundlers analyze code, they create something called a dependency graph.

Example project:

src/  
 ├── main.js  
 ├── utils.js  
 └── api.js
Enter fullscreen mode Exit fullscreen mode

Dependency graph:

         main.js  
        /      \ 
       v        v  
   utils.js    api.js
Enter fullscreen mode Exit fullscreen mode

If utils.js exports multiple functions but main.js uses only one, Tree Shaking removes the rest.


Tree Shaking Works Best With ES Modules

Tree Shaking depends on static analysis, which is easier with ES modules.

ES Module Syntax

import { add } from "./math.js";  
export function add() {}
Enter fullscreen mode Exit fullscreen mode

Because imports and exports are static, bundlers can analyze them before execution.


Why CommonJS Is Harder to Tree Shake

CommonJS uses dynamic imports.

Example:

const math = require("./math");  

console.log(math.add(2, 3));
Enter fullscreen mode Exit fullscreen mode

Because require() can be dynamic, bundlers cannot always determine exactly which functions are used.

Comparison

Module System Syntax Tree Shaking
ES Modules import/export Excellent
CommonJS require/module.exports Limited

Real-World Library Example

Let’s look at a simplified example of how Tree Shaking works with a utility library.

Suppose a utility library exports multiple functions:

export function debounce() {}  
export function throttle() {}  
export function memoize() {}  
export function deepClone() {}  
export function isEqual() {}
Enter fullscreen mode Exit fullscreen mode

Your app only imports:

import { debounce } from "./library.js";
Enter fullscreen mode Exit fullscreen mode

Without Tree Shaking:

All 5 functions included
Enter fullscreen mode Exit fullscreen mode

With Tree Shaking:

Only debounce() included
Enter fullscreen mode Exit fullscreen mode

Example: Tree Shaking With Lodash

A common real-world example of Tree Shaking involves the Lodash utility library.

Lodash contains many helper functions such as:

  • debounce
  • throttle
  • cloneDeep
  • isEqual
  • memoize

If you import Lodash like this:

import _ from "lodash";
Enter fullscreen mode Exit fullscreen mode

Your bundle may include the entire library, even if you only use one function.

A better approach is to import only the function you need:

import debounce from "lodash/debounce";
Enter fullscreen mode Exit fullscreen mode

Or using lodash-es, which supports ES modules:

import { debounce } from "lodash-es";
Enter fullscreen mode Exit fullscreen mode

Tree Shaking vs Dead Code Elimination

These terms are related but slightly different.

Technique Purpose
Tree Shaking Removes unused exports
Dead code elimination Removes unreachable code
Minification Compresses code

Tree Shaking focuses on removing unused module exports, while dead code elimination removes code that can never run.

Example of dead code:

if (false) {  
  console.log("never runs");  
}
Enter fullscreen mode Exit fullscreen mode

This code can be removed by optimizers.


Side Effects and Tree Shaking

A side effect happens when code runs immediately when a module is imported.

Example:

console.log("Module loaded");  

export function log(message) {  
  console.log(message);  
}
Enter fullscreen mode Exit fullscreen mode

Even if log() isn't used, the console.log runs.

Because of this, bundlers must be careful when removing modules.

Another common example of side effects is importing CSS:

import "./styles.css";
Enter fullscreen mode Exit fullscreen mode

Even though nothing is exported from this file, the import has a side effect - it loads styles into the page.

Because of this, bundlers should not remove this import, even if it appears unused.


Pure Module vs Side Effect Module

Pure module

export function add(a,b){ return a+b }
Enter fullscreen mode Exit fullscreen mode

Safe to remove if unused.

Side-effect module

console.log("loaded")  
export function add(){}
Enter fullscreen mode Exit fullscreen mode

May need to remain.


Code Patterns That Prevent Tree Shaking

Bad pattern:

const utils = {  
  add(a,b){ return a+b },  
  subtract(a,b){ return a-b }  
}  

export default utils
Enter fullscreen mode Exit fullscreen mode

Better pattern:

export function add(a,b){ return a+b }  
export function subtract(a,b){ return a-b }
Enter fullscreen mode Exit fullscreen mode

Named exports make Tree Shaking easier.


Tree Shaking with Webpack

Webpack supports Tree Shaking in production mode.

Example package.json:

{  
 "scripts": {  
   "build": "webpack --mode production"  
 }  
}
Enter fullscreen mode Exit fullscreen mode

Production builds activate optimizations like:

  • Tree Shaking

  • minification

  • dead code removal


Using sideEffects in package.json

Libraries can help bundlers using:

{  
 "sideEffects": false  
}
Enter fullscreen mode Exit fullscreen mode

This tells bundlers that modules do not run code during import, allowing unused files to be safely removed.


Real-Life Application Example

Imagine an e-commerce platform.

Utility functions include:

  • calculateTax

  • calculateShipping

  • generateInvoicePDF

  • exportSalesReport

  • formatPrice

The product page only needs:

calculateTax  
formatPrice
Enter fullscreen mode Exit fullscreen mode

Tree Shaking ensures the browser does not download the reporting or PDF generation code.

This improves performance significantly.


Tree Shaking in Modern Tools

Tool Tree Shaking Support
Webpack Yes
Rollup Excellent
Vite Yes
esbuild Yes

Most modern build tools support Tree Shaking by default.


How To Enable Tree Shaking

Tree Shaking usually works automatically when using modern bundlers with ES module syntax in production builds.

Using Webpack

Run the production build:

webpack --mode production
Enter fullscreen mode Exit fullscreen mode

Webpack automatically enables:

  • Tree Shaking
  • Dead code elimination
  • Minification

Using Vite

npm run build
Enter fullscreen mode Exit fullscreen mode

Vite uses Rollup under the hood, which performs Tree Shaking automatically.

Using Rollup

rollup -c
Enter fullscreen mode Exit fullscreen mode

Rollup was originally designed around ES modules, making its Tree Shaking very powerful.


Best Practices for Tree Shaking

Practice Why
Use ES modules Easier static analysis
Prefer named exports More precise imports
Avoid unnecessary side effects Enables safe removal
Import only what you need Prevents large bundles
Use production builds Activates optimizations

Example Project

utils.js

export function sum(a,b){ return a+b }  
export function multiply(a,b){ return a*b }  
export function divide(a,b){ return a/b }
Enter fullscreen mode Exit fullscreen mode

main.js

import { sum } from "./utils"  

console.log(sum(4,6))
Enter fullscreen mode Exit fullscreen mode

Production bundle should contain only the sum function.


Final Mental Model

Think of Tree Shaking as:

Tree Shaking keeps only the code your application actually uses and removes everything else during the build process.

Your application becomes:

  • faster

  • lighter

  • more efficient

without changing how you write features.


Common Mistakes That Break Tree Shaking

  1. Using CommonJS (require) instead of ES modules
  2. Importing entire libraries instead of specific functions
  3. Adding side effects in utility modules
  4. Converting ES modules to CommonJS with Babel before bundling
  5. Testing only in development mode instead of production builds

Final Thoughts

Tree Shaking is a fundamental optimization technique in modern JavaScript development. As applications grow and dependencies increase, removing unused code becomes critical for performance.

By structuring your code properly and using ES modules, you allow bundlers to eliminate unnecessary code automatically.

This leads to:

  • smaller bundles

  • faster applications

  • better user experiences

And ultimately, cleaner production builds.

Top comments (0)