Step 1 Types are checked, then erased
The compiler tsc does two separate jobs: it type-checks your code, and it emits JavaScript. Crucially, the emit step simply deletes all the type annotations, interfaces and generics — they leave no trace at runtime. Your running program is ordinary JavaScript; the types were only ever a compile-time conversation with the checker.
Pick a snippet — see what ships to the browser
Notice the right side has no types. Because types vanish, you cannot check a type with if at runtime — that is why typeof and other runtime checks still matter.
Step 2 Catching mistakes before the code runs
The payoff for annotating types is that the checker can prove whole classes of bugs cannot happen, and tell you at edit time instead of at 3am in production. It tracks the type of every value and complains when you use one incorrectly.
Click each line — would tsc accept it?
Green lines type-check; red lines are errors the compiler reports before the program ever runs. None of this changes the emitted JavaScript — it only decides whether compilation succeeds.
Step 3 Structural typing — shape is all that matters
Most languages use nominal typing: a value fits a type only if it was declared with that type's name. TypeScript is structural: a value fits if it has the right shape. If it has the required properties of the right types, it is assignable — no implements needed. (This is "duck typing", checked statically.)
Is each object assignable to Point?
interface Point { x: number; y: number }
Extra properties are fine for an existing value; missing or wrong-typed required properties are not. The name of the object's type is irrelevant — only its structure.
Step 4 Inference — types you do not have to write
You rarely annotate everything. TypeScript infers types from the right-hand side of assignments, from return statements, and from how values are used. Annotations are mostly for function boundaries and intent; inside, the checker figures it out.
What does TypeScript infer here?
Note const infers a narrower literal type than let — const s = "hi" is the literal type "hi", while let s = "hi" widens to string.
Step 5 Generics — types as parameters
A generic lets a function or type work over any type while keeping the relationship between input and output. Instead of returning any (which throws away safety), a type parameter <T> threads the caller's actual type through.
Instantiate function identity<T>(x: T): T
The compiler substitutes T with the type you pass, so the result type is exact — identity(42) returns number, not any. The same machinery powers Array<T>, Promise<T>, Map<K, V> and the rest.
Step 6 Glossary
| Term | In one sentence |
|---|---|
type erasure | Types are deleted during compile; nothing remains at runtime. |
structural typing | Compatibility is decided by shape, not by declared name. |
inference | The compiler deduces types you did not write. |
narrowing | Inside an if (typeof x === "string"), x is treated as a string. |
union | string | number — a value that is one of several types. |
generic | A type parameter <T> that keeps input/output types linked. |
any vs unknown | any disables checking; unknown forces you to narrow first. |
The whole idea: annotate intent → the checker proves it → the types are erased → JavaScript runs. TypeScript adds zero runtime cost and zero runtime guarantees — its value is entirely in catching mistakes before you ship.