skip to content

What does `"sideEffects": false` in a package's package.json actually claim, and what goes wrong when a package makes that claim untruthfully?

level: seniorimportance: should knowfreq 42%

answer

  1. a promise, not an analysis
  2. about evaluation, not usefulness
  3. array form names the impure files
  4. nothing verifies the claim
  5. fails only in the production build

basics

~20 s

It claims that no file in the package does anything observable merely by being evaluated, so a bundler may skip importing any module whose exports go unused. It is an unverified promise; a false one silently deletes polyfills, styles and registrations from production builds.

solid answer

~50 s

`sideEffects` is a bundler convention, not part of Node's package.json specification. Setting it to `false` tells the bundler: evaluating any module in this package is unobservable, so if an importer uses none of a module's exports, drop the import and skip the evaluation entirely — no purity analysis required. You can also give an array of globs naming the files that *do* have effects, such as `["*.css", "./src/polyfills.js"]`; anything not matched is assumed pure. Omitting the field means "assume everything has effects", which is why unmarked packages shake poorly. The danger is that nothing verifies the claim. A package that patches built-ins, registers a custom element, imports a stylesheet, or wires a singleton at import time, but declares `sideEffects: false`, will have that code silently removed — and only in the production build, where the failure is hardest to trace.

code

json · 11 lines
json
{
  "name": "@acme/ui",
  "version": "3.1.0",
  "type": "module",
  "main": "./dist/index.js",
  "sideEffects": [
    "*.css",
    "./dist/polyfills.js",
    "./dist/register-elements.js"
  ]
}

go deeper

for a junior

Know that this field lives in package.json, that false means "nothing here does work just by being imported", and that stylesheet and polyfill imports are the usual reason the claim is wrong.

for a middle

Explain what the claim licenses precisely — skipping evaluation of a module whose exports are unused — and describe the array form as the honest default, with globs naming the files that really do have effects.

for a senior

Demonstrate the audit and the diagnosis: reading module bodies for non-declaration top-level statements before setting the flag, and recognising a production-only missing-behaviour bug as a symptom of a false claim rather than a pipeline problem.

for a principal

Own it as a published contract. Decide whether your org's packages may declare it at all, what review or CI check backs the claim, and how you handle the fact that a downstream consumer's broken build is your package's regression, discoverable only in their production output.

## Why the field exists Deciding whether evaluating a module is observable is undecidable in general, so bundlers default to keeping any module they cannot prove inert. That default is correct and expensive: it means an unmarked package's modules survive even when you import nothing from them. `sideEffects` is the escape hatch — a way for the package author, who actually knows, to assert what the tool cannot derive. It is worth being precise about the status of the field: it is not standardised, Node ignores it entirely, and it has nothing to do with `exports`, `type`, or resolution. webpack introduced it and other bundlers adopted it; Rollup honours it through its node-resolve plugin, and esbuild, Vite and Parcel read it too. Treat it as an ecosystem convention with broad support, not a language feature. ## The three shapes ```json { "sideEffects": false } ``` No file in this package does anything observable on evaluation. If none of a module's exports are used, the import may be removed and the module never evaluated. ```json { "sideEffects": ["*.css", "./src/polyfills.js", "./src/register-*.js"] } ``` These globs, relative to the package root, name the files that *do* have effects; those are always evaluated when imported. Everything else is treated as pure. This is the shape most real libraries should use, because almost every library has at least one such file. ```json { } ``` Field absent, or `true`. Assume every file has effects. Safe, and the reason unmarked dependencies bloat bundles. ## What the claim licenses, precisely The claim is about *evaluation*, not about the code's usefulness. It licenses exactly one transformation: when an importer ends up using none of the bindings a module provides, delete the import edge rather than keeping the module for its effects. It does not license deleting used exports, does not make impure functions safe to call, and does not apply to the effects of *calling* the package's API — only to what happens at import time. Note also that the flag governs the files of the package that declares it. Declaring it in your application's own `package.json` makes the same claim about your application's source files, which is a much stronger statement than most teams realise when they copy the line in. ## How a false claim fails The failures share a shape: the build succeeds, tests that import the module directly still pass, dev mode looks fine (because dev builds usually skip elimination), and the production bundle is quietly missing behaviour. - **Polyfills.** `import 'core-js/stable'` or a module patching `Array.prototype` is removed; older engines throw `TypeError` on a missing method at runtime. - **Styles.** `import './theme.css'` is removed and the UI ships unstyled — the classic symptom that sends people looking at the CSS pipeline instead of the flag. - **Registration.** `customElements.define(...)`, plugin registries, i18n message bundles and route tables that register on import silently never register; components never upgrade, routes 404. - **Singletons and instrumentation.** A module-level client, error handler or performance mark that was installed at import time simply is not there. The trace is unpleasant because the missing code left no trace: there is no error at build time, no warning, and the stack trace at runtime points at the *consumer* of the missing behaviour, not at the module that was deleted. ## Auditing your own package Before adding the flag, read every module body and ask what a consumer could observe if it never ran. In practice: 1. Grep for top-level statements that are not declarations — assignments, calls, `new`, `throw`. 2. Check for imports of CSS or other assets. 3. Check for anything that mutates imported or global state. 4. List every file that fails and put it in the array form rather than dropping the flag entirely. Then verify empirically: build a small consumer that imports one trivial export, inspect the output for the modules you expect to be gone, and run the app. ## Interview framing The strong answer says three things: what the claim means operationally (skip evaluation of unused modules), that nothing checks it, and what the failure mode looks like (production-only missing behaviour). The weak answer describes it as "a flag that makes tree shaking work", which mistakes a hand-written assertion for an analysis.

  • How is `"sideEffects": ["*.css"]` different from omitting the field entirely?
    Omitting it means "assume every file has effects", so no module is skipped and the package shakes poorly. The array inverts the default: only the listed globs are treated as impure and always evaluated when imported, while every other file is assumed pure and may be skipped when its exports go unused. It is the honest middle ground for a library with a few genuinely effectful files.
  • A team adds `sideEffects: false` to their application's own package.json and the UI ships unstyled. What happened?
    The claim covers the declaring package's own files, including the application source. Any module whose only job was `import './x.css'` used no exports, so the import edge was deleted and the stylesheet never entered the build. Fix it with the array form listing `"*.css"`, or by removing the blanket claim. The symptom appears only in production builds, since dev builds usually skip elimination.
  • Does `sideEffects: false` let a bundler remove a call to an exported function that the app actually uses?
    No. The claim is strictly about import-time evaluation. Used exports remain, and calling them at runtime does whatever they do — the flag makes no promise about function bodies. What it licenses is skipping the evaluation of a module whose exports nobody reads. Removing an individual unused call expression is a different mechanism, driven by `/*#__PURE__*/` annotations or local purity analysis.

saying these in an interview costs you the question

  • Calls it a standard Node package.json field
  • Thinks the bundler verifies the claim before trusting it
  • Believes it makes any package tree-shakeable regardless of contents
  • Assumes it also removes unused calls inside used modules
  • Copies it into an app package.json without auditing CSS imports

context