Viscosity
Review
Part I · JavaScript for engine builders · Day 4

13. TypeScript I: types as labels

One misspelt property can quietly turn a whole simulation into NaN. TypeScript reads your code before it runs, points at the mistake, and then vanishes without a trace.

About 16 minutes, including the exercises and quiz
By the end you will be able to
  • Add type annotations to variables, parameters and return values.
  • Describe the shape of an object with an interface.
  • Explain what the type checker catches, and when it does its work.
  • Explain type erasure, and why data from outside still needs checking at runtime.

A typo that doesn’t crash

Here is one step of a particle simulation. Find the bug:

function step(p, dt) {
  p.x = p.x + p.vX * dt;
}

const p = { x: 0, y: 0, vx: 2, vy: 1 };
step(p, 0.5);
console.log(p.x); // NaN

It’s vX with a capital X. The object has no property by that name, so p.vX is undefined, undefined * 0.5 is NaN (“not a number”), and NaN plus anything is NaN. There’s no error message. In a real program, a thousand particles quietly turn into NaN, and you find out an hour later, somewhere else.

JavaScript can’t complain, because it has no idea what p is supposed to look like. You know. You just never wrote it down. TypeScript is a way to write it down, so that a program can check it for you.

Intuition

Think of labels on kitchen jars: “sugar”, “salt”, “flour”. Before you start cooking, a careful friend reads the whole recipe and says “step 4 puts salt in the sugar jar”. The labels don’t change the food at all. They let someone catch the mistake before the cake is ruined.

Labels on names

let count: number = 0;
let name: string = 'Ada';

function mul(a: number, b: number): number {
  return a * b;
}

mul('3', 4);
// error: Argument of type 'string' is not
// assignable to parameter of type 'number'.

A colon and a type after a name is a label. a: number says “a will always hold a number”. The : number after the parameter list labels what the function returns. When a call breaks a label, the checker reports it, with the line and the reason.

You don’t have to label everything. Write let n = 3; and TypeScript works out the label number by itself. Then n = 'three' is an error too: “Type ‘string’ is not assignable to type ‘number’.”

Definition

TypeScript is JavaScript plus syntax for types, and a program that checks them. A type annotation, like : number, labels what values a variable, parameter or return value may hold. Type checking means reading the whole program, before it runs, and reporting every place where a value could break a label. Where you leave a label out, the checker infers one from the value[2].

Interfaces: the shape of an object

Numbers and strings are easy to label. For objects, you describe their shape: which properties they have, and what each one holds.

Intuition

An interface is a blank form with labelled lines. An object fits the form if it fills in every line with the right kind of value. Ask for a line the form doesn’t have, and the checker says so.

On the left, the interface Particle: a form with four lines, x, y, vx and vy, each labelled number. On the right, an object filling every line with a number, ticked. Below, reading p.vX fails because the form has no line called vX.THE INTERFACE (a blank form)Particlex: numbery: numbervx: numbervy: numberAN OBJECT (a filled-in form)px: 0✓y: 0✓vx: 2✓vy: 1✓p.vX✗ there is no line called vX on this form
An interface describes a shape; any object with that shape fits. Particle is a form with four lines, each a number. The object p fills every line, so it fits. p.vX asks for a line that isn’t on the form, so the checker rejects it.
interface Particle {
  x: number;
  y: number;
  vx: number;
  vy: number;
}

function step(p: Particle, dt: number) {
  p.x = p.x + p.vX * dt;
  // error: Property 'vX' does not exist on
  // type 'Particle'. Did you mean 'vx'?
}

There’s the typo from the start of the chapter, caught before the program ever ran. A missing property is caught the same way: writing const q: Particle = { x: 1, y: 2, vx: 0 }; gets “Property ‘vy’ is missing”. Lists are labelled with []: number[] is an array of numbers, and Particle[] is an array of particles.

Definition

An interface gives a name to an object shape: the properties it must have, and the type of each. Any object with those properties fits. You don’t need a class or any special way of making it: only the shape matters.

Where do the types go?

Here’s the surprising part. Step through what happens to a TypeScript file on its way to running:

Step throughFrom TypeScript to running JavaScript
The pipeline: step.ts goes through tsc, which checks and erases types, giving step.js, which the engine runsstep.tstscstep.jsengine
interface Particle {
x: number;
vx: number;
}
function step(p: Particle, dt: number) {
p.x = p.x + p.vX * dt;
}
You write step.ts. The highlighted parts are type syntax: labels for the checker. Everything else is ordinary JavaScript.
1/6

The TypeScript compiler, tsc, does two jobs. First it checks the whole file. Then it erases the types and writes out plain JavaScript. Engines only ever run that JavaScript. One more surprise: by default tsc writes the JavaScript even when it found errors, and you have to ask it not to with the --noEmitOnError flag[1].

Definition

Type erasure means the types are removed before the code runs. The output is ordinary JavaScript with the labels deleted, so type annotations never change what a program does[1]. All the checking happens before the program starts.

Pitfall

This book’s runnable boxes accept TypeScript, but they only erase the types (with a tool called sucrase) and run what’s left. They never check them, so a type error here gets no warning. To see the real checker, install the typescript package and run tsc on your file. In this chapter, the tests check what your code does, and the text tells you what tsc would say.

Try it: the box below runs exactly what erasure leaves.

Try itWhat erasure leaves behind
// The runner erases types and runs
// what is left. It never checks them.
function add(a, b) {
  return a + b;
}

console.log(add(2, 3));
console.log(add('2', 3));

// Try: write a: number and b: number,
// then run again. Same output! tsc
// would flag add('2', 3) instead.

Labels are promises, not guards

The checker only sees your code. Data arriving from outside the program, like text from a server or a file, was never checked:

const p: Particle = JSON.parse(text);

JSON.parse turns text into an object, and its result has the special type any, which switches checking off. So the checker takes your word that this is a Particle. If the text was {"x": "oops"}, you only find out later, at runtime, as a wrong answer.

At the border, you check by hand. JavaScript’s typeof operator gives the runtime kind of a value as a string: typeof 3 is 'number' and typeof 'hi' is 'string'. One famous oddity: typeof null is 'object'. That’s exactly what this chapter’s exercise needs.

Under the hood

Because types are erased, an engine like V8 never sees your interfaces. It gets plain JavaScript and works out the shapes of objects by itself, while the program runs[3]. Viscosity is different: it reads your source, TypeScript included, so an interface like Particle is a free hint about which fields your objects have. But a type is a promise about your code, not a guarantee about data (remember JSON.parse). So the analyses you’ll build later can use types as hints, but they still have to prove anything they rely on.

Your turn

ExerciseCheck a particle at runtime

Types are erased, so data from outside the program is never checked for you. Write isParticle(v) that returns true only when v is an object (not null) whose x, y, vx and vy properties are all numbers, and false otherwise. Extra properties are fine. (The runner erases types, so the tests check behaviour. The walkthrough shows the typed version.)

interface Particle {
  x: number;
  y: number;
  vx: number;
  vy: number;
}

function isParticle(v: any): boolean {
  // your code here
}

const ok = { x: 0, y: 0, vx: 1, vy: 1 };
console.log(isParticle(ok));
Extra practiceThe bug tsc would have caught

tsc rejects this function with “Type 'string' is not assignable to type 'number'” on the return line. Run it first to see what goes wrong at runtime, then fix it so totalX(ms) returns the sum of the x values, and 0 for an empty list.

interface Mover {
  x: number;
  vx: number;
}

function totalX(ms: Mover[]): number {
  let t = '0';
  for (const m of ms) {
    t = t + m.x;
  }
  return t;
}

const ms = [
  { x: 1, vx: 0 },
  { x: 2, vx: 0 },
];
console.log(totalX(ms));

What you learned

  • TypeScript is JavaScript plus type annotations like x: number, and a checker, tsc.
  • Type checking happens before the program runs. Where you leave a label out, TypeScript infers one.
  • An interface names an object shape. Any object with that shape fits.
  • Type erasure: tsc deletes the types and emits plain JavaScript, so annotations never change behaviour. Engines never see them.
  • Types describe your code, not outside data. At the border, check with typeof. This book’s runner erases types but doesn’t check them.

Next: types that say “this or that”, and how the checker follows your ifs.

Check your understanding

0/6
  1. 1.

    With function mul(a: number, b: number): number, what happens to the line mul('3', 4); when you run tsc?

  2. 2.

    What does tsc output for the line let n: number = 3;?

  3. 3.

    Which object fits interface Point { x: number; y: number }?

  4. 4.

    You run plain tsc file.ts (no flags) and it reports a type error. What about the JavaScript file?

  5. 5.

    const p: Particle = JSON.parse(text); type-checks fine. Why can p.x still be a string at runtime?

  6. 6.

    Why can’t V8 use your TypeScript interfaces to speed up your program?

References

  1. [1] The TypeScript team (Microsoft). The Basics (TypeScript Handbook). typescriptlang.org. www.typescriptlang.org/docs/handbook/2/basic-types.html (checked 2026-10-31)
  2. [2] The TypeScript team (Microsoft). Everyday Types (TypeScript Handbook). typescriptlang.org. www.typescriptlang.org/docs/handbook/2/everyday-types.html (checked 2026-10-31)
  3. [3] Mathias Bynens, Benedikt Meurer (2018). JavaScript engine fundamentals: Shapes and Inline Caches. mathiasbynens.be. mathiasbynens.be/notes/shapes-ics (checked 2026-10-31)