Why types

Understand what a type checker does for you, what it cannot do, and the one idea the rest of the course rests on.

01of 14
≈ 10Min read
7Parts

It is a reader, not a runtime

TypeScript is JavaScript plus annotations, and a program that reads them. At build time the annotations are erased: no checks are inserted, nothing is validated at runtime, and the file that ships is ordinary JavaScript.

That single fact explains most of the surprises. A type says what you intend; it cannot stop a server sending something else.

the same program, before and after
// what you write
function total(doors: Door[]): number {
  return doors.reduce((n, d) => n + d.price, 0);
}

// what runs — the types are removed, nothing is added
function total(doors) {
  return doors.reduce((n, d) => n + d.price, 0);
}

What it actually catches

  • Typos in property and function names — the single most common bug in JavaScript.
  • Arguments in the wrong order, or the wrong shape.
  • A value that might be undefined being used as though it certainly is not.
  • A branch you forgot to handle when a union grew a new member.
the errors it finds before anyone else does
door.nmae             // Property 'nmae' does not exist. Did you mean 'name'?
total(door)           // Argument of type 'Door' is not assignable to 'Door[]'
door.price.toUpperCase()   // Property 'toUpperCase' does not exist on type 'number'
if (door.days = 5)    // assignment, not comparison — flagged
JSON.parse(text).naem // still not caught: parse returns any

What it does not catch

  • Wrong logic. A function that returns the wrong number returns it with the right type.
  • Anything about data from outside your program, unless you validate it.
  • Race conditions, null from the DOM at the wrong moment, or a library lying in its own types.
A type is a promise about data you have not seen

const door: Door = await res.json() type-checks perfectly and tells you nothing about what the server actually sent. Anything crossing a boundary — a network response, a form, localStorage, a URL — needs a runtime check as well. That is what zod and friends are for.

Structural typing: the one idea

A value fits a type when it has the right shape — it does not have to be declared as that type. This is why TypeScript describes existing JavaScript so well.

The exception is an object literal passed directly, which gets an extra check for surplus properties, on the reasonable assumption that a stray key there is a typo.

a type is a shape, not a name
interface Door { name: string; days: number }

function ship(door: Door) { … }

const thing = { name: "Painted", days: 5, colour: "sage" };
ship(thing);          // fine: it has everything a Door needs

ship({ name: "Painted", days: 5, colour: "sage" });
//    ^ Object literal may only specify known properties

What you get back for the effort

  • Autocompletion that is right. The editor knows the shape, so it can offer the fields that exist.
  • Rename with confidence. Change a field and every use of it moves, including the ones you forgot.
  • Documentation that cannot rot, because the compiler checks it.
  • A smaller test suite — the tests that only asserted "this is a string" are gone.

And what it costs

  • A build step, and a compiler to keep happy.
  • Types for your dependencies — usually shipped, occasionally missing, sometimes wrong.
  • The temptation of any, which turns the checker off exactly where the code is hardest.
  • Error messages that get long when generics get deep.

Where it is worth it

Anything more than one file and one person. The value grows with the size of the codebase and the number of people changing it, and it is highest at the seams — the function everybody calls, the shape everything passes around.

For a fifty-line script it is overhead. For an application with a server, a client and a shared model, it is the cheapest documentation you will ever write.

Practice