isolatedModules in TypeScript is less about building faster and more about ensuring your JavaScript output is predictable and type-safe even in complex, multi-file projects.

Let’s see it in action. Imagine a simple setup:

src/utils.ts:

export const greeting = "Hello";

src/main.ts:

import { greeting } from "./utils";

console.log(`${greeting}, world!`);

If you compile this with isolatedModules disabled (the default), TypeScript might try to be clever. It sees main.ts imports utils.ts, so it might try to infer that utils.ts is only ever used in this one place and could potentially optimize it away or bundle it in a way that breaks if utils.ts were used elsewhere.

Now, enable isolatedModules in your tsconfig.json:

tsconfig.json:

{
  "compilerOptions": {
    "target": "ES2016",
    "module": "ESNext",
    "isolatedModules": true,
    "noEmit": false, // We want to see the JS output
    "outDir": "./dist"
  },
  "include": ["src/**/*.ts"]
}

And compile with tsc. The key here is that when isolatedModules is true, TypeScript treats each file independently during the type-checking phase. It assumes each file will be compiled into its own JavaScript module. This means it can’t make assumptions about how other files will import or use its exports.

What problem does this solve?

This flags a whole class of errors that only appear at runtime, especially in environments that bundle JavaScript modules (like Webpack, Rollup, or esbuild). Without isolatedModules, TypeScript might allow code like this:

src/bad-example.ts:

// This file is only used in a single place, so TS might not complain.
export function processData(data: string): number {
  return data.length;
}

src/another-file.ts:

// Imagine this file is compiled separately and then imported by a bundler.
import { processData } from "./bad-example";

// If processData was *only* ever used with strings, TS wouldn't catch this.
const result = processData(123 as any); // Runtime error waiting to happen
console.log(result);

With isolatedModules: true, TypeScript would immediately flag src/bad-example.ts with an error like:

error TS1208: All files must be able to be part of an import/export in a module. 'src/bad-example.ts' is a script file.

It’s telling you that bad-example.ts is not structured to be a standalone module. It needs to have top-level import or export statements, or be explicitly marked as a module.

How does it work internally?

The core mechanism is that isolatedModules forces TypeScript to perform type checking on a per-file basis. When a file is type-checked in isolation, TypeScript cannot rely on information from other files to validate its imports and exports. It must ensure that its own export statements are well-typed and that any import statements it makes refer to valid, exported symbols from other modules.

This means that if a file exports something that could be used in a way that loses type information (e.g., exporting a function that takes a string but the file itself only ever calls it with a number), isolatedModules will complain. It can’t guarantee that the export will be used correctly elsewhere because it’s not checking the "elsewhere" during this file’s check.

The exact levers you control:

  1. isolatedModules: true: This is the primary switch. It enforces the per-file, no-cross-file-inference rule during type-checking.
  2. module compiler option: isolatedModules works best with module formats like ESNext, ES2015, ES2020, ES2022, NodeNext, or Node16. These are all designed for explicit module systems. Using CommonJS with isolatedModules: true can lead to unexpected behavior or errors if not carefully managed, as CommonJS historically allowed more implicit global scope interactions.
  3. Top-level import and export: Every file you want to be part of this isolated module system must have at least one top-level import or export statement, or be an ambient declaration file (.d.ts). If a file has no imports or exports, TypeScript treats it as a script file, which is incompatible with isolatedModules.

The one thing most people don’t know is that isolatedModules is fundamentally about ensuring that each TypeScript file, when compiled, generates JavaScript that is self-contained and doesn’t rely on implicit global state or cross-file type inference that a bundler might break. It’s a contract between your TypeScript compiler and your module bundler: "Trust me, each of my files is a valid, independently compilable module, and I’ve written them in a way that preserves type safety regardless of how you combine them."

The next concept you’ll run into is managing the output of these isolated modules, particularly when dealing with declaration files (.d.ts) and ensuring they correctly reflect the module structure.

Want structured learning?

Take the full Typescript course →