What TypeScript Actually Is
A checker that runs before your code does, and then gets out of the way entirely. Everything it knows is gone by the time anything executes, and that one fact explains most of its behaviour.
By the end: Explain what survives compilation and what vanishes, and why that matters.
TypeScript is a checker that runs before your program does, and then disappears completely.
That second half is the part worth sitting with. Every type you write is deleted before anything executes. There is no TypeScript at runtime, no type information to inspect, nothing to ask questions of. The compiler reads your code, tells you what it thinks, strips its own annotations out, and hands the browser or Node plain JavaScript.
Almost everything that surprises people about TypeScript follows from that one fact.
The same program, twice
Here is a function you could have written yesterday.
function greet(name) {
return 'Hello, ' + name;
}
You will read in a lot of places that all JavaScript is valid TypeScript. That is true of the syntax and misleading in practice, because with the settings any sensible project uses, the function above is rejected. The compiler does not know what name is, and it has been told not to guess.
function greet(name) {
return 'Hello, ' + name;
}
console.log(greet('Ada'));
What the compiler says
error TS7006: Parameter 'name' implicitly has an 'any' type.
noImplicitAny is the flag doing that, and it is on because strict is on. Without it the compiler would silently treat name as “could be anything”, which compiles and checks nothing — the worst of both worlds. So: say what name is.
function greet(name: string): string {
return 'Hello, ' + name;
}
console.log(greet('Ada'));
// The return type is checked too, so a typo in the function body is caught
// rather than shipped.
function shout(name: string): string {
return greet(name).toUpperCase();
}
console.log(shout('grace'));
Output
Now try to misuse it. This is the first panel in the course that does not compile, and it will not be the last: a course about a type checker has to show the checker refusing things.
function greet(name: string): string {
return 'Hello, ' + name;
}
// A number is not a string, and the compiler will not pretend otherwise.
console.log(greet(42));
What the compiler says
error TS2345: Argument of type 'number' is not assignable to parameter of type 'string'.
Erasure, demonstrated
The claim is that types vanish. Rather than assert it, here is a program that reports on itself. Everything you see printed is present at runtime; anything type-shaped is already gone.
interface Person {
name: string;
age: number;
}
type Mood = 'happy' | 'grumpy';
function describe(person: Person, mood: Mood): string {
return `${person.name} is ${person.age} and ${mood}`;
}
const ada: Person = { name: 'Ada', age: 36 };
console.log(describe(ada, 'happy'));
// Everything below is what survives. Notice what is absent: there is no
// record anywhere that `ada` was declared as a Person, or that 'happy' was
// checked against Mood. The object is just an object.
console.log('the object itself:', ada);
console.log('its keys: ', Object.keys(ada));
console.log('its prototype: ', Object.getPrototypeOf(ada) === Object.prototype ? 'Object.prototype' : 'something else');
console.log('a plain object?', JSON.stringify(ada) === JSON.stringify({ name: 'Ada', age: 36 }));
console.log('');
console.log('Person and Mood were instructions to the compiler.');
console.log('Nothing you can write here could find them, because they are gone.');
What this compiles to
function describe(person, mood) {
return `${person.name} is ${person.age} and ${mood}`;
}
const ada = { name: 'Ada', age: 36 };
console.log(describe(ada, 'happy'));
// Everything below is what survives. Notice what is absent: there is no
// record anywhere that `ada` was declared as a Person, or that 'happy' was
// checked against Mood. The object is just an object.
console.log('the object itself:', ada);
console.log('its keys: ', Object.keys(ada));
console.log('its prototype: ', Object.getPrototypeOf(ada) === Object.prototype ? 'Object.prototype' : 'something else');
console.log('a plain object?', JSON.stringify(ada) === JSON.stringify({ name: 'Ada', age: 36 }));
console.log('');
console.log('Person and Mood were instructions to the compiler.');
console.log('Nothing you can write here could find them, because they are gone.');
Output
You cannot even ask whether they are gone. Writing typeof Person is not a way of discovering that Person is absent at runtime — it is a compile error, because Person is not the name of a value and never was.
interface Person {
name: string;
age: number;
}
// There is no value called Person to take the typeof.
console.log(typeof Person);
What the compiler says
error TS2693: 'Person' only refers to a type, but is being used as a value here.
interface Person {
name: string;
}
function isPerson(value: unknown): boolean {
// Person exists only during checking. At runtime there is nothing here.
return value instanceof Person;
}
console.log(isPerson({ name: 'Ada' }));
What the compiler says
error TS2693: 'Person' only refers to a type, but is being used as a value here.
Where the checking happens
Three separate things get confused constantly, so it is worth naming them plainly.
- Type checking reads your code and reports mistakes. This is what
tscdoes, and what your editor is doing continuously as you type. - Erasure deletes the annotations and produces JavaScript. Cheap, mechanical, and it does not care whether the checking passed.
- Running executes that JavaScript. By this point TypeScript has no involvement at all.
They are independent, and that has a practical consequence people find alarming at first: code with type errors still compiles and still runs. Type errors are not build failures unless you ask for them to be.
// The compiler flags the line below, and the emitted JavaScript runs anyway,
// because concatenating a number with a string is legal JavaScript. The type
// error is a warning about intent, not a physical impossibility.
const count: number = 3;
console.log('items: ' + count);
// This is the honest framing: TypeScript tells you what you probably did not
// mean. It does not change what the machine is capable of doing.
console.log([1, 2, 3].map((n) => n * 2));
Output
Node can run TypeScript, but it does not check it
Modern Node runs a .ts file directly. This is genuinely convenient and it is also a trap, because it does the erasure and skips the checking.
// node app.ts
// Strips the annotations and runs the result. Fast, no configuration.
// Notices nothing about types. A file full of type errors runs happily.
//
// npx tsc --noEmit
// Checks everything and produces no output files. This is the one that
// tells you whether your types actually hold together.
//
// Use both: Node to run, tsc to check. Your editor is doing the second one
// continuously already, which is why the editor is where you notice mistakes.
export {};
What this buys you
Three things, in rough order of how much they matter day to day.
Mistakes surface where you make them. A misspelled property, a function called with arguments in the wrong order, a value that might be missing — these become squiggles under your cursor rather than incidents in production.
Your editor becomes accurate. Autocomplete stops guessing. Rename actually renames the right things. Jump-to-definition lands in the right place. This is a bigger daily improvement than the bug catching, and it is the part people miss when they go back.
The types document the code. A signature that says function send(to: EmailAddress, body: string): Promise<Receipt> answers questions a comment would have had to, and unlike a comment it cannot quietly go out of date.
interface Order {
id: string;
items: { sku: string; quantity: number }[];
placedAt: Date;
}
function total(order: Order): number {
return order.items.reduce((sum, item) => sum + item.quantity, 0);
}
const order: Order = {
id: 'A-1001',
items: [
{ sku: 'desk', quantity: 1 },
{ sku: 'chair', quantity: 4 },
],
placedAt: new Date('2026-01-15T09:30:00Z'),
};
console.log('order:', order.id);
console.log('items:', total(order));
console.log('placed:', order.placedAt.toISOString().slice(0, 10));
// Every one of these would have been a compile error:
// order.itmes misspelled, TS2551
// order.placedAt.slice Date has no slice, TS2339
// total({ id: 'x' }) missing properties, TS2345
Output
What it does not buy you
Being clear about the limits saves disappointment later.
- It does not validate data from outside your program. A JSON response you have annotated as
Orderis assumed to be anOrder. If the server sends something else, nothing complains and you get a runtime error later, further away. Lesson 13 is about closing that gap. - It does not make your code faster. The output is the same JavaScript you would have written.
- It does not catch logic errors. A correctly typed function can still return the wrong answer. This is why the algorithms course is about algorithms and this one is about shapes.
- It is not sound. There are deliberate holes where the compiler trusts you, because closing them would make ordinary code painful to write. Lessons 06 and 12 show you exactly where they are.
Your turn
- Create a file called
scratch.tsanywhere and put a function in it that takes two numbers and returns their sum. Annotate both parameters and the return type. - Run it with
node scratch.ts. Confirm it works. - Now call it with a string instead of a number. Run it again with
node scratch.tsand notice that nothing complains and it prints something silly. - Now run
npx tsc --noEmit scratch.tsand read the error. That difference is the entire point of this lesson. - Finally, add
const x: number = 'hello';and predict the error code before you run the check. It is one of the two most common codes in the language.
Recap
- TypeScript is JavaScript plus annotations that are checked before your code runs and deleted before it runs.
- Because the types are erased, nothing type-shaped exists at runtime — which is why
instanceofworks on a class and cannot work on an interface. - Checking, erasure and running are three separate steps. Code with type errors still compiles and still runs.
node file.tsstrips types without checking them;tsc --noEmitchecks them without writing anything. You want both.- The biggest daily gain is not caught bugs, it is an editor that finally knows what your code means.