JavaScript Dead Code Elimination in UglifyJS and Terser

Dead code elimination (DCE) is an optimization technique that removes unreachable, unused, or redundant code from an application to reduce file size and improve load times. Modern JavaScript minifiers like UglifyJS and Terser perform this process by parsing source code into an Abstract Syntax Tree (AST), analyzing control flows and variable usages, pruning unnecessary AST nodes, and generating a compressed output file.

What Is Dead Code Elimination?

Dead code elimination targets two primary categories of code:

  1. Unreachable Code: Code that can never be executed under any runtime condition (e.g., code following a return or throw statement, or statements inside an if (false) block).
  2. Unused Code (Dead Stores): Variables, functions, or expressions that are defined or evaluated but never referenced elsewhere, provided their evaluation creates no side effects.

By stripping this code before production deployment, developers reduce bundle sizes, decrease memory consumption, and speed up JavaScript parsing and execution times in the browser or runtime environment.

How UglifyJS and Terser Process ASTs

Both UglifyJS and Terser (a modern fork of UglifyJS optimized for ES6+) rely on AST manipulation rather than regular expressions or string matching to ensure code transformations are semantically safe. The elimination process occurs across distinct phases:

1. Parsing Code into an AST

The minifier reads the JavaScript source code and converts it into a tree data structure (AST). Each node in the tree represents a syntactic construct, such as an IfStatement, BinaryExpression, FunctionDeclaration, or Identifier.

2. Scope and Symbol Analysis

Terser and UglifyJS traverse the AST to build a symbol table. This step maps variables and functions to their respective lexical scopes. The minifier tracks: * How many times an identifier is referenced. * Whether a variable is reassigned. * Whether a function is invoked or exported.

If an identifier has zero references and does not escape its scope, its corresponding AST node becomes a candidate for removal.

3. Constant Folding and Conditional Evaluation

Minifiers evaluate static expressions at compile time (known as constant folding). For example:

if (DEBUG && 1 + 1 === 2) {
    console.log("Debugging");
}

If DEBUG is set to false, the condition statically evaluates to false. The minifier converts the AST IfStatement node into an empty block or eliminates the node entirely, preventing the console.log branch from appearing in the output.

4. Control Flow Pruning

The AST traversal inspects execution flow boundaries. When a jump statement—such as return, throw, break, or continue—is encountered within a block, any subsequent sibling nodes in that block are marked unreachable. The tree transformer splices these dead nodes out of the AST body.

5. Side-Effect Analysis (Purity Checking)

Before dropping unused expressions or function calls, the minifier checks for potential side effects.

If an expression contains both side effects and unused values, Terser optimizes the AST by retaining only the side-effect portion (e.g., transforming const x = getVal() into getVal()).

6. AST Reconstruction and Code Generation

After multiple compression and tree-shaking passes, the modified AST contains only the nodes necessary for execution. The code generator traverses the final, pruned AST to produce the final, minified JavaScript string.