TypeScript has a simple story: types are checked, then erased, and what runs is JavaScript. That's true for almost every line you write. The exceptions are worth knowing, because they're the lines that behave differently from how they look.

The part that disappears

interface User { name: string; age: number; } import type { Config } from "./config"; const users: User[] = JSON.parse(raw) as User[]; const theme = { dark: true } satisfies Record<string, boolean>; function greet(u: User): string { return `Hi ${u.name}`; } 

Run it through any TypeScript compiler and you get, give or take whitespace:

const users = JSON.parse(raw); const theme = { dark: true }; function greet(u) { return `Hi ${u.name}`; } 

The interface, the import type, the annotations, as and satisfies: all gone. None of it exists at runtime, which is why as User[] can't check anything.

The part that doesn't

A few TypeScript features generate JavaScript of their own.

Enums become an object, built by a small function:

enum Level { Low, High } 

turns into roughly:

var Level; (function (Level) { Level[Level["Low"] = 0] = "Low"; Level[Level["High"] = 1] = "High"; })(Level || (Level = {})); 

That's why Level[0] is "Low": a numeric enum maps both ways. The exact shape varies by compiler; the idea doesn't.

Parameter properties become assignments:

class Point { constructor(public x: number, public y: number) {} } 
class Point { constructor(x, y) { this.x = x; this.y = y; } } 

Namespaces with values become an object built by a function too, like enums.

Why this matters now

Node can run .ts files by stripping types, without a compile step. Stripping only works for syntax that can be erased. An enum or a parameter property needs code generated, so Node's default mode refuses it.

TypeScript 5.8 added a flag for exactly this: erasableSyntaxOnly reports an error on enums, parameter properties and value namespaces, so your code stays strippable. The usual replacement for an enum is a plain object:

const Level = { Low: 0, High: 1 } as const; type Level = (typeof Level)[keyof typeof Level]; 

Seeing it for your own code

You learn this fastest by looking at the output. I do it in JSLab, a JavaScript & TypeScript scratchpad for macOS: Actions → Show Transpiled Output puts the JavaScript your tab actually runs in the side bar, next to your code.

  • It refreshes on each run, and marks itself Stale once you've edited past the run it shows.
  • Hide instrumentation switches between the code exactly as it runs and the code without JSLab's own additions for Auto Log, logpoints and loop protection. Turn it off once; it's a good look at how those features work.
  • Which syntax proposals the transform accepts, such as the pipeline operator, is set in Settings → Build. Decorators work as they are.

Types are checked in the editor and stripped when the code runs, so a type error is an underline, never a failed run.

JSLab launches Nov 15. Show Transpiled Output: https://js-lab.app/docs/editor