· 6 min read ·

TypeScript 6.0 Makes You Annotate What It Used to Infer

Source: typescript

The headline from TypeScript 6.0 is straightforward: the compiler is now written in Go and runs roughly ten times faster on large codebases. The TypeScript repository itself drops from about fifteen seconds to check down to around one and a half. For teams running type checking in CI pipelines or waiting on language server responses in large monorepos, that is a meaningful difference.

The performance gain is not simply the same algorithm running on faster compiled code. The Go compiler fans out across files in parallel using goroutines. That parallelism is the source of the 10x number, and it required a specific architectural precondition: files need to be type-checkable without resolving the entire dependency graph first. That constraint has a name in TypeScript 6.0. It is --isolatedDeclarations.

What the Flag Actually Requires

--isolatedDeclarations shipped experimentally in TypeScript 5.5. TypeScript 6.0 stabilizes it and integrates it more directly into the parallel build model. The requirement is specific: every exported declaration must carry enough type annotation that a tool can generate its .d.ts entry by reading only that file, without looking at anything else.

In practice this means exported functions need explicit return type annotations, and exported constants need explicit type declarations:

// Rejected: return type requires cross-file inference
export function fetchUser(id: string) {
  return db.users.findOne({ id });
}

export const defaultRetries = 3;

// Required
export function fetchUser(id: string): Promise<User> {
  return db.users.findOne({ id });
}

export const defaultRetries: number = 3;

For internal functions, nothing changes. The constraint applies only to exported declarations, which are the points where other modules form dependencies on your types.

TypeScript’s inference is strong enough that most of these annotations are redundant from a correctness standpoint. The compiler already knows the return type of fetchUser. The flag does not add information to the type system; it adds information to the source file in a form that does not require resolving the whole program to read.

Why This Matters for Declaration Emit

The traditional declaration emit process is sequential. To generate the .d.ts for a module, the compiler resolves its imports, checks the types of all referenced symbols, and propagates that information up through the type graph. In a monorepo with hundreds of packages, this serializes into a long waterfall where each package waits on its dependencies before producing declarations.

With --isolatedDeclarations, each file can emit its .d.ts independently. Tools like oxc and esbuild already support this mode. Rollup has added support in recent versions. The Go compiler uses the same principle internally to parallelize its type checking pass across your project’s files.

The performance difference for large projects can be substantial. The TypeScript team’s benchmarks on the TypeScript compiler’s own repository show that the checker fanout accounts for most of the 10x build improvement. Incremental builds in watch mode see smaller gains because the bottleneck there shifts to I/O and cache validation, but cold builds and CI pipelines where caching is unreliable benefit proportionally.

The Annotation Trade-Off

TypeScript has spent a decade building inference strong enough that you rarely need to write types explicitly. That design philosophy has been consistent. let x = 5 does not need : number. A function that concatenates two strings does not need : string on its return.

--isolatedDeclarations carves out a specific exception: public API boundaries. The argument the TypeScript team has made is that exported function signatures are the points in a codebase where types serve as documentation, not just correctness constraints. An explicit return type on an exported function tells a consumer what to expect without reading the implementation. If the implementation changes the return shape, the annotation forces a decision about whether that change is intentional.

That argument is reasonable, and many TypeScript style guides already require return type annotations on exported functions for exactly this reason. The flag converts an existing convention into a compile-time requirement, which is how TypeScript tends to handle stricter practices: make them optional first, then progressively elevate them over releases.

The annotation overhead is real but bounded. You are not writing types everywhere; you are writing them at the edges of your modules. For a well-structured codebase with clear internal and public surfaces, the annotation burden is smaller than it sounds when you first read the flag’s description.

The Connection to —erasableSyntaxOnly

--isolatedDeclarations is not the only flag in TypeScript 6.0 that asks you to write your code differently. --erasableSyntaxOnly, introduced in 5.8 and tightened in 6.0, requires that you avoid syntax that generates JavaScript code: no enum, no namespace, no parameter properties.

Both flags are addressing the same underlying question. Can your TypeScript be processed per-file, without cross-module resolution and without code generation? For --isolatedDeclarations, the answer is: yes, if your public API boundaries are explicitly annotated. For --erasableSyntaxOnly, the answer is: yes, if you stick to syntax that is purely annotation.

The combination of both flags is what enables the full range of modern TypeScript workflows. With both on, your TypeScript files can be type-checked in parallel by the Go compiler, their declarations can be generated without program-level analysis, and they can be run directly in Node.js 24, Deno, or Bun without a build step.

Node.js 24 ships native TypeScript execution via Amaro, which wraps SWC to strip type annotations. SWC can only erase; it cannot transform enums or expand parameter properties into constructor assignments. --erasableSyntaxOnly is the compile-time check that guarantees your code is compatible with that execution model. A tsconfig.json for a Node 24 project targeting both native execution and parallel builds looks like this:

{
  "compilerOptions": {
    "erasableSyntaxOnly": true,
    "isolatedDeclarations": true,
    "module": "nodenext",
    "moduleResolution": "nodenext"
  }
}

Neither flag is enabled by default, and both require migration work for codebases that rely on enums or leave return types implicit on exports. For existing projects, the practical approach is incremental: enable --isolatedDeclarations first and use the errors to identify which exports need annotations. Then assess --erasableSyntaxOnly based on how much of the codebase uses enums or parameter properties, and whether the migration to as const objects is feasible.

What Stays the Same

It is worth being clear about what these flags do not require. TypeScript’s inference inside function bodies is unchanged. Local variables, intermediate computations, and private class members do not need annotation. The stricter requirements are scoped to the external surface of your modules.

The Go compiler also maintains the same type-checking semantics as the JavaScript compiler. Your existing tsconfig.json works unchanged. Type errors that were errors before are still errors. Type errors that were not errors before are still not errors. The Go port replicates the JavaScript compiler’s behavior faithfully, and the TypeScript team has been tracking compatibility issues in the microsoft/typescript-go repository since the project was announced in early 2025.

Tooling authors who rely on semi-public compiler API internals, including ts-morph, ts-jest, and ESLint’s TypeScript parser, need to test against 6.0. The stable public API is preserved, but the internal object graph that some tools reach into has changed. The JavaScript compiler remains available during the transition period.

Looking at TypeScript 7

TypeScript 6.0 is explicitly positioned as a preparatory release. The Go compiler ships as the new default, the legacy options are cleaned up, and the flags the parallel build model needs are stabilized. TypeScript 7, when it arrives, is expected to be the full Go port without a JavaScript fallback.

The annotation contract that --isolatedDeclarations introduces is the same contract the Go compiler relies on internally to parallelize its work. Building that contract into your codebase now means you are aligned with the assumptions the next generation of TypeScript tooling is being built on. The trade-off is a few more annotations at module boundaries. The return is a type checker that can use the full width of modern hardware to do its work.

Was this interesting?