Emmanuel Akinfulubi

Lesson 01 of 1814 min read

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.

Ordinary JavaScript

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.

The same function, under the settings this course uses

TS7006
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.

The same function, with the parameter described

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.

Passing the wrong type

TS2345
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.

What is left after the types are removed

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.

Types are not values, and the compiler says so plainly

TS2693
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.

An interface is not something that exists

TS2693
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.

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.

An error the compiler objects to, running perfectly well

// 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.

Two commands that look similar and do very different things

// 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.

A shape the compiler now knows everything about

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.

Your turn

  1. Create a file called scratch.ts anywhere and put a function in it that takes two numbers and returns their sum. Annotate both parameters and the return type.
  2. Run it with node scratch.ts. Confirm it works.
  3. Now call it with a string instead of a number. Run it again with node scratch.ts and notice that nothing complains and it prints something silly.
  4. Now run npx tsc --noEmit scratch.ts and read the error. That difference is the entire point of this lesson.
  5. 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 instanceof works 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.ts strips types without checking them; tsc --noEmit checks 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.

Saved in this browser only.

Newsletter

New writing, when it lands.

Occasional notes on shipping software with AI agents. No schedule, no filler, unsubscribe in one click.