How it works
The explanation, with examples you can run.
Check, then emit
tsc does two independent jobs: it type-checks the program, and it writes JavaScript. By default it writes the JavaScript even when there are type errors (noEmitOnError is off), because the errors are about your intent, not about whether the code can run. Many tools only do the second job (see the build topic), which is why the type check has to run somewhere on its own, in your editor and in CI.
What the output looks like
interface User { id: number name: string} function greet(user: User): string { return `Hello, ${user.name}`} const ada = { id: 1, name: "Ada" } as Userconsole.log(greet(ada))Compiled with tsc --target es2022, this becomes:
"use strict";function greet(user) { return `Hello, ${user.name}`;}const ada = { id: 1, name: "Ada" };console.log(greet(ada));The interface is gone, user: User is just user, and the as User cast has vanished. A cast never converts or checks anything: it only tells the checker to believe you.
Two consequences follow:
- You cannot ask about a type at runtime.
instanceof Userdoes not compile whenUseris an interface, andtypeof x === "User"is never true. To branch on data at runtime you need something that exists at runtime: atypeofresult, a property you test within, a class, or a tag field (see discriminated unions). - Types cannot change behaviour. Two programs that differ only in their types compile to the same JavaScript. If the program crashes, the types were wrong about the data, not the other way round.
The exceptions
A few TypeScript features are not just types, so deleting them would break the program:
enumbecomes an object (with reverse mappings for numeric members).namespacewith values becomes a function wrapper.- Constructor parameter properties (
constructor(private name: string)) become assignments.
TypeScript 5.8 added erasableSyntaxOnly, which reports these as errors. It exists because tools now run TypeScript by blanking out the types and nothing else. Node.js does this since 22.18 and 23.6: node app.ts replaces the type syntax with spaces and runs the result, without type-checking it, and it refuses an enum with ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX. A union of string literals (type Role = "admin" | "reader") or a plain as const object usually does the job of an enum without generating code.
Downlevelling is a separate question
target decides which JavaScript syntax the output may use. With an old target, tsc rewrites newer syntax (optional chaining, class fields) into older equivalents. That rewriting is about JavaScript versions, not types, and many projects leave it to a bundler instead.
Check yourself
3 questions. Get them all right to finish this topic.
0 of 3 right so far. Get all three to finish this topic.