Skip to content
RungsySign in

TypeScript Basics

Types as documentation the compiler checks — and the escape hatches that quietly turn it off.

45 minDifficulty 2/5language · typesAI-writtenWritten by a model on 16 August 2026 and not yet read by a person. Checked automatically: schema, the pedagogical rules the hand-written material is held to, and every diagram parsed for real.

Before this

Why this exists

A function expects a number, receives a string that looks numeric from an API response, and silently produces `"5" + 1 = "51"` instead of `6` — a bug that surfaces three files away from where the actual mistake happened. TypeScript catches this at the moment you write the code, not three files and one production incident later, by checking that the types actually agree.

The mental model

TypeScript is JavaScript with a type checker bolted on at compile time — it disappears entirely by the time your code runs. Every type annotation is a claim you're making that the compiler verifies against how the value is actually used; if the claim and the usage disagree, you get an error before you ever run the code, not a runtime surprise.

How it works

Types are erased at compile time — they have zero runtime cost or presence

`function add(a: number, b: number): number` compiles to plain `function add(a, b)` — every type annotation is stripped out entirely. TypeScript never checks types while your code is running; it only checks them once, ahead of time, which means a type error can never be 'caught' with a try/catch, because by runtime the types are simply gone.

any turns off checking; unknown keeps it on

`any` tells the compiler 'stop checking this value entirely' — you can call any method, access any property, and get zero errors, correctly or not. `unknown` says 'I genuinely don't know this value's type yet' but still requires you to narrow it (with a type guard) before doing anything type-specific with it — it's the safe version of the same 'I don't know the type' situation.

Structural typing: shape matters, not the declared name

TypeScript compares types by their SHAPE, not by name — an object literal with the right properties satisfies an interface even if it was never explicitly declared as implementing it. Two differently-named interfaces with identical properties are interchangeable; this is fundamentally different from languages like Java where a class must explicitly declare which interfaces it implements.

Narrowing lets the compiler track type refinement through your code's control flow

After `if (typeof value === 'string')`, the compiler knows, for every line inside that block, that `value` is specifically a string — this is called narrowing, and it's how TypeScript reconciles broad initial types (like a union) with the specific type your logic actually guarantees at any given point in the code.

The mechanism

The compiler reads your annotated source and checks that every usage is consistent with the declared or inferred types — a mismatch is reported as a compile-time error, before anything runs. Once checking passes, every type annotation is stripped away entirely, producing plain JavaScript that executes with zero trace of TypeScript ever having been involved.

flowchart LR
  TS[TypeScript source\nwith type annotations] --> C[Type checker\ncompile time only]
  C -->|errors if types disagree| E[Compile error]
  C -->|types verified| S[Strip all types]
  S --> JS[Plain JavaScript\nruns exactly as written]
Diagram source for TypeScript Basics.

What people get wrong

TypeScript checks types at runtime, so a wrong type will throw an error when the code actually executes.
All type checking happens at compile time; by the time your code runs, every type annotation has been erased entirely — a value that violates its declared type at runtime (because it came from an untrusted external source, say) causes no runtime error from TypeScript at all. This is exactly why data from an API response, cast with `as SomeType` without actually validating its shape, can silently violate its claimed type at runtime with zero warning — TypeScript trusted the cast and moved on.
Using `any` is basically the same as not using TypeScript for that value.
That's essentially correct, and it's precisely why `any` should be rare and deliberate rather than a default escape hatch — every `any` is a hole in the type checker's coverage, silently disabling exactly the safety net TypeScript exists to provide. Codebases that reach for `any` whenever the correct type is annoying to write end up with type coverage that looks comprehensive in the file tree but is actually full of unchecked gaps.
unknown and any behave the same way, just with different names.
`any` lets you do anything with the value with no checking at all; `unknown` requires you to actually narrow the type (with a type guard) before performing any type-specific operation on it, preserving safety while still expressing genuine uncertainty about the type. Confusing the two means missing out on `unknown`'s actual value: honestly representing 'I don't know this type yet' without disabling the type checker entirely, unlike `any`.

When not to use it

Genuinely migrating a large existing JavaScript codebase incrementally.
Allow `any` temporarily at the boundaries of unmigrated code, but track it (many linters can flag every `any` usage) and treat it as technical debt to reduce over time, not a permanent state.
Data is arriving from an external, untrusted source — an API response, user input, a file.
Runtime validation (like a schema library) at that boundary, not just a type assertion — TypeScript's compile-time checking cannot verify that data crossing a runtime boundary actually matches the claimed type.

Terms

Type erasure
The process by which TypeScript strips all type annotations during compilation, producing plain JavaScript with zero runtime trace of the type system.
Structural typing
A type system that compares types by their shape (which properties/methods exist) rather than by declared name or explicit inheritance.
Narrowing
The compiler refining a broader type to a more specific one within a block of code, based on a runtime check like typeof or an equality comparison.
unknown
A type representing a value whose type isn't yet known, requiring explicit narrowing before any type-specific operation is allowed on it.

In an interview

Why doesn't TypeScript catch a bug where an API response is cast with `as User` but the actual JSON doesn't match the User shape?

  • TypeScript's checking happens entirely at compile time and is erased before runtime
  • an `as` cast tells the compiler to trust your claim without verifying it against the runtime value
  • a real check requires runtime validation, e.g. a schema library, not just a type assertion

Can you recall it?

What does it mean that TypeScript's type checking is 'erased' at compile time, and why does that mean a type assertion (as SomeType) provides no actual runtime safety?

Keep track of this

Add TypeScript to your map and Rungsy will schedule reviews so you actually remember it.