TypeScript 7, the native compiler and migration
TypeScript adds static types to JavaScript, checked before the code runs. In July 2026 its compiler was rewritten from JavaScript into Go, producing builds eight to twelve times faster while keeping type checking behaviour identical.
That is the biggest change to the language in years and simultaneously the most invisible, since it adds not one new piece of syntax. All that changes is how long you spend waiting.
What actually changed in version 7
The compiler had been written in TypeScript and run on a JavaScript runtime, so it compiled itself. The new version is that code rewritten in Go, with native code, shared memory, and multithreading.
The effect is measurable and lands on what hurts daily. A full type check on a large project drops from minutes to seconds. Editor completions stop stalling on a big file, since the language service runs on the same engine.
More important than the numbers is that the type checking rules were preserved: seven checks exactly as six does, so this is not a new language. The configuration did change, though. strict is now on by default, module defaults to esnext, types defaults to empty rather than pulling in every declaration package it finds, and rootDir defaults to the project directory. Former warnings turned into hard errors alongside that: target: es5, baseUrl, moduleResolution: classic plus node and node10, and module: amd, umd and systemjs are no longer supported. A project coming from version five is best taken through six first, since six is where those deprecations landed.
That does not make migration unconditional. Version 7.0 ships no programmatic API at all, and a new one is expected only in 7.1. Tools reaching for the compiler from code rather than the command line run in the meantime on the compatibility package @typescript/typescript6, which provides a tsc6 command and exposes the 6.0 interface next to the seven binary. The practical consequence is that workflows with Vue, MDX, Astro and Svelte, and type checking inside Angular templates, still rest on version 6.0. Check the specific tools you use before migrating, since that is the only real blocker.
When TypeScript genuinely pays off
Worth settling honestly, since the answer is not "always".
It pays off on code somebody will read in six months, and on a team larger than one person. Types then act as documentation that cannot go stale, since the compiler checks it.
It pays off during refactoring. Renaming a field on an object used in forty places is a mechanical operation in typed code and a hunt for breakage in untyped code.
It does not pay off in a single use script or a prototype meant to test a hypothesis and be thrown away. Configuration, dependency type declarations, and arguing with the compiler cost more than they return there.
A third case sits in between: a small project that might grow. A sensible route there is plain JavaScript with types described in documentation comments, checked by the compiler with no build step. You get a good share of the benefit without changing the process.
Things worth setting from the start
The default configuration is lenient, and leniency in this language means fewer errors caught.
{
"compilerOptions": {
"strict": true,
"noUncheckedIndexedAccess": true,
"exactOptionalPropertyTypes": true,
"verbatimModuleSyntax": true
}
}The first option is obvious and has been the default since seven, so the entry in the file only pins that state down. The second is the most valuable of the neglected ones: it makes reading an array element by index yield a type that may hold no value, which matches reality.
const days = ['mon', 'tue', 'wed']
const day = days[5]
console.log(day.toUpperCase())Without that option the compiler accepts the third line and the program breaks only at runtime. With it, the error is reported where the problem actually is, forcing you to state what happens when the element is missing.
The third distinguishes an absent field from a field set to an empty value, which matters for objects sent to external interfaces. The fourth forces explicit marking of type imports.
import type { User } from './types'
import { saveUser } from './api'That distinction is not cosmetic. A type import disappears at build time without a trace, while an import without that keyword stays and pulls in the whole module along with its side effects, even if all you use from it is a type definition.
A separate piece of advice for a project only now adopting types: enable these options one at a time rather than all at once. Each produces its own class of errors, and mixing them in one pass gives a list of several hundred entries that nobody will review.
The types that make a difference
Beyond the basics, three mechanisms account for most of the real value.
Types narrowed by a check let you describe a state where certain fields exist only in certain situations.
type Response =
| { status: 'success'; data: User }
| { status: 'error'; message: string }
function handle(r: Response) {
if (r.status === 'success') {
console.log(r.data.name)
} else {
console.log(r.message)
}
}That removes an entire class of bugs where somebody reaches for data on an error response. The compiler simply refuses.
The second mechanism is inference rather than declaration. A type derived from an actual value cannot drift from it, while a hand written one can. So instead of describing a server response shape with a separate interface, derive it from the validation schema, in Zod for instance, and hold one source of truth.
The third is utility types that transform existing types instead of creating new ones.
type User = {
id: string
email: string
passwordHash: string
createdAt: Date
}
type PublicUser = Omit<User, 'passwordHash'>
type UserUpdate = Partial<Pick<User, 'email'>>
type FetchResult = Awaited<ReturnType<typeof fetchUser>>Picking a few fields, making everything optional, or extracting a function's return type are operations that save maintenance, since a change at the source propagates automatically. The first of these carries extra value here: adding a new sensitive field to the model will not leak it through the interface, as long as the public shape is built by subtraction rather than by listing fields from scratch.
Know the boundary, though. Advanced types can express a great deal and that is tempting, but a type whose error message runs thirty lines costs a team more than it gives. A simpler type plus a comment is often the better answer than a virtuoso construction.
Moving an existing project across
Migrating a project written in JavaScript is the most common situation and simultaneously the easiest way to discourage a team.
Start by enabling checking on the code you already have, without renaming files. The compiler can analyse JavaScript files and report errors that cannot be dismissed: typos in property names, calls with the wrong argument count, references to things that do not exist. That is usually the first handful of real defects and a good argument in the conversation about whether the whole exercise makes sense.
The second step is conversion order. Begin with files carrying no internal dependencies, helper functions and constants, then work upward. The reverse order means every converted file drags missing types from the rest of the project behind it.
The third is ensuring new code arrives already typed. Without that rule migration never ends, since the rate of adding untyped files can exceed the rate of converting old ones.
The fourth concerns files that cannot move yet. Rather than blocking everything, disable checking in that specific file with a comment and add it to a list. A targeted exemption with a note is more honest than loosening the configuration globally, since it stays visible how much remains.
Finally, agree on a measure of progress. Files converted against files total, posted once a week, does more to finish a migration than any declaration, since it shows whether the work moves at all.
Checking performance on a large project
Even after the compiler rewrite, a large project can check more slowly than its size suggests, and the cause usually sits in the code rather than the tool.
The first suspect is conditional and recursive types of high complexity. The compiler expands them at every use, and nested constructions can multiply work exponentially. One overly clever utility type used in a hundred places can dominate the checking time of an entire project.
The second is no split into composite projects. A large repository checked as one unit recomputes everything on every change, while splitting it into separate units with saved state lets it skip what did not change.
The third is imports pulling in more than needed. Reaching for one type from a package exporting a thousand forces the compiler to read the whole declaration file.
Diagnosis is simple: built in counters show how much time went to parsing and how much to type checking, and which file cost the most. Measure before guessing, since intuition here misses more often than it lands.
TypeScript against the alternatives
| Option | Checking | Build step | Pick it when |
|---|---|---|---|
| TypeScript | Before running, full | Yes | A project maintained longer than a quarter |
| JavaScript with types in comments | Before running, narrower | No | Small project, no appetite for configuration |
| Plain JavaScript | None | No | A single use script, a prototype |
| Runtime validation | During execution | Depends | Outside data that types do not cover |
The last row is not an alternative but a complement, and confusing the two is the most common misunderstanding around this language. Types vanish at compile time, so a server response described as an object of a given shape can in reality be anything. The compiler believes it, and the program breaks only when reaching a missing field.
The right arrangement is this: validation at the system boundary, types inside. Everything arriving from the network, a form, a file, or environment variables passes a runtime check, and from there you work on types derived from that check.
Daily work and the ecosystem
In React types concern mainly component props and state, and that is where the return shows fastest, since passing the wrong prop is the most common slip during changes.
On the server side the value grows with shared code. tRPC derives client types straight from server code, so changing a procedure signature raises an error in a component rather than at a user. Inference in query libraries works similarly, with the data shape coming from the fetching function.
If the type system still reads like a set of rules to memorise, working through the interactive Visual Types lessons beats reading another description. Generics, conditional types, and infer land far more easily when you watch the result change as the input changes than they do from syntax alone.
Dependency types also deserve attention. Libraries today usually publish them alongside the code, and those that do not mostly have separate declaration packages. A dependency without types introduces an island where the compiler checks nothing, and that island tends to grow.
The last item is compiling versus checking. Most build tools strip types without checking them, since that is faster. That means a project can build despite type errors if nobody runs checking separately. Add it as a distinct pipeline step, otherwise all this work is only editor hints. Runtimes that execute .ts files directly behave the same way, Bun among them: types are stripped with no compile step, so code starting quickly says nothing about whether it is correct.
Common mistakes
The first is using the any type as a solution to a problem. It disables checking where it appears and spreads further through assignments. When you genuinely do not know the type, use the unknown type, which forces a check before use.
The second is casting instead of validating. An assertion telling the compiler that a server response has a given shape is a claim with nothing behind it, and moves the error from compile time to runtime.
The third is no type checking in the build pipeline. A tool stripping types without checking lets errors through, and the team learns about them from production.
The fourth is configuration that is too loose. Without strict mode, a good share of the errors this language gets chosen for simply go undetected.
The fifth is annotating types the compiler would infer. A manual annotation on a variable initialised with a value adds no safety while adding a place to update on every change.
The sixth is building complicated types where a simple one suffices. The error message from such a type is often unreadable, and whoever meets it a year later loses more time than the author saved.
FAQ
How does TypeScript 7 differ from earlier versions?
By a compiler rewritten from JavaScript into Go, producing builds eight to twelve times faster. Syntax and type checking rules stayed compatible with version 6.0, so the code itself compiles identically, while the defaults in tsconfig.json changed.
Is migrating to version 7 risky?
Type checking stays compatible with version 6.0, while the default configuration changed and some former options, target: es5 or baseUrl among them, are no longer supported. The second risk is tools reaching for the compiler from code: 7.0 has no programmatic API yet, so Vue, Astro, Svelte, MDX and Angular templates rest on the compatibility package built from six.
Do types protect against bad data from a server?
No. Types vanish at compile time, so a response described as an object of a given shape can be anything. At the system boundary you need runtime validation, and types should be derived from it.
Is TypeScript worth it on a small project?
If the project will live longer than a few weeks and more than one person will touch it, yes. On a single use script or a throwaway prototype the configuration costs more than it gives, and types described in documentation comments are the middle route.
Where do I start converting an existing project?
By enabling checking on the existing JavaScript, without renaming files. Then move file by file, starting with those without dependencies, and enable strict options one at a time so the error count in any single pass stays reviewable.
The seventh release is described in the team's announcement, and full documentation sits on the language site.