Command Palette

Search for a command to run...

Hectal
PHASE 12Intermediate ~28 min· topic 6 of 6

Topic 12.6

TypeScript, a First Look

In one line

TypeScript is JavaScript plus type annotations: a checker reads the types before your code runs and reports mistakes such as passing a string where a number is expected, then the types are removed and plain JavaScript runs in the browser.

Think of it like this

Labelled sockets and plugs. A plug only fits the socket shaped for it, so you can't plug a kettle into a phone charger by mistake. Types are those shapes for your data: a function that says "I take a number" won't accept a string. The check happens when you try to plug in (while writing code), not after the kettle catches fire (when users run it). Once everything fits, the labels aren't needed any more, so they are peeled off before shipping.

Words you'll meet

New words in this topic, in plain English. Come back here whenever one feels fuzzy.

Type
A description of what kind of value something is, such as number, string or an object with certain properties.
Dynamic typing
Types are only checked while the program runs. JavaScript works this way.
Static typing
Types are checked before the program runs, by reading the code. TypeScript adds this.
Superset
A bigger language that contains all of another one. All JavaScript is valid TypeScript syntax.
Annotation
A type written after a colon, like price: number.
Type inference
TypeScript working out a type by itself from the value, so you don't have to write it.
Interface
A named description of an object's shape: which properties it has and their types.
Union type
A type that allows one of several options, written with |, like string | number.
Type erasure
Removing all types when turning TypeScript into JavaScript; nothing about them is left at run time.
Type guard
A run-time check, such as typeof x === "string", that proves a value's type.

Step by step

01Install TypeScript and create a config

TypeScript is a dev dependency (Topic 12.3). tsconfig.json tells tsc which files to check and how strict to be. These options suit a modern browser project bundled by Vite.

tsconfig.jsonwhole filejson
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "ESNext",
    "moduleResolution": "bundler",
    "strict": true,
    "noEmit": true,
    "lib": ["ES2022", "DOM"]
  },
  "include": ["src"]
}
terminal
$ npm install -D typescript
── expected output ──
added 1 package, and audited 2 packages in 2s
 
found 0 vulnerabilities

02Write some TypeScript

The interface describes an item. The function says it takes an array of Item and returns a number. The last line has a bug: "2" is a string, perhaps read from an input box.

src/cart.tswhole filetypescript
interface Item {
  name: string;
  price: number;
  qty: number;
}

export function cartTotal(items: Item[]): number {
  return items.reduce((sum, item) => sum + item.price * item.qty, 0);
}

const pen: Item = { name: "Pen", price: 20, qty: "2" };

03Let the checker find the bug

tsc --noEmit checks without writing any files. The error gives the file, line and column, an error code you can search for, and a clear message, before a single user runs the code.

terminal
$ npx tsc --noEmit
── expected output ──
src/cart.ts:11:45 - error TS2322: Type 'string' is not assignable to type 'number'.
 
11 const pen: Item = { name: "Pen", price: 20, qty: "2" };
~~~
 
src/cart.ts:4:3
4 qty: number;
~~~
The expected type comes from property 'qty' which is declared here on type 'Item'
 
 
Found 1 error in src/cart.ts:11

04See what the JavaScript looks like

Fix the bug (qty: 2) and compile. The interface and every annotation are gone; the logic is unchanged. This is type erasure: nothing in the output checks types any more.

dist/cart.jswhole filejavascript
export function cartTotal(items) {
  return items.reduce((sum, item) => sum + item.price * item.qty, 0);
}
const pen = { name: "Pen", price: 20, qty: 2 };

05How the pieces fit

In a Vite project, npm run dev strips types instantly so the page updates fast, while your editor and tsc --noEmit (in a script and in CI) do the real checking.

How the pieces fitdiagram
Rendering diagram…

06A few everyday type features

Unions limit a value to known options, ? marks an optional property, and narrowing means TypeScript learns a more exact type inside an if that checks it.

src/theme.tswhole filetypescript
type Theme = "light" | "dark";

interface Settings {
  theme: Theme;
  fontSize?: number;          // optional: may be missing
}

function label(id: string | number): string {
  if (typeof id === "number") {
    return "#" + id.toFixed(0);  // here TypeScript knows id is a number
  }
  return id.toUpperCase();       // and here, a string
}

const s: Settings = { theme: "blue" };
// error TS2322: Type '"blue"' is not assignable to type 'Theme'.

Try it yourself

  1. 1

    Change the input

    In the first example, type 10 into the box and press Run again. Predict all three console lines before you look.

  2. 2

    Strengthen the guard

    In the third example, add the response '{"name":"","age":-5}'. Predict whether it's accepted. Then change isUser to also require a non-empty name and an age of 0 or more.

  3. 3

    Try the real checker

    Open the TypeScript Playground (typescriptlang.org/play), paste the src/cart.ts code from the walkthrough, and read the red underline. Fix it, then open the .JS tab on the right to see the erased output.

Code & diagrams

The bug TypeScript would catch, in plain JavaScript New tab

JavaScript ran without complaint and produced `32` items. In TypeScript, `function addItems(inCart: number, extra: number)` makes `addItems(3, fromInput)` a compile error (`Argument of type 'string' is not assignable to parameter of type 'number'`), because `input.value` is typed as `string`.

Preview

Press Run to see the page.

Console

Nothing logged yet.

Expected console output

5
32
5
Types in JSDoc comments: still plain JavaScript New tab

The browser ignores the comments and runs the code. Paste this into a `.js` file in VS Code: with `// @ts-check` at the top, changing `qty: 3` to `qty: "3"` gets a red underline from the TypeScript engine, no build step needed.

Preview

Press Run to see the page.

Console

Nothing logged yet.

Expected console output

Total: 310
Types are gone at run time: check outside data yourself New tab

`typeof null` is `"object"` (an old JavaScript quirk), which is why the guard also checks `value !== null`. Without this guard, `data.name.toUpperCase()` on the third response would throw `TypeError: data.name.toUpperCase is not a function`, even in a fully typed TypeScript program.

Preview

Press Run to see the page.

Console

Nothing logged yet.

Expected console output

{"name":"Asha","age":31} -> valid User, ASHA
{"name":"Ravi"} -> rejected
{"name":42,"age":"x"} -> rejected
null -> rejected

Break it on purpose

Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.

Break #1

Run TypeScript in the browser

In the first example, change the first line to function addItems(inCart: number, extra: number) { and press Run.

terminal
$ Console
── what you'll see ──
Uncaught SyntaxError: Unexpected token ':'

Break #2

Trust the API's JSON

In TypeScript, write const user: User = await res.json(); console.log(user.name.toUpperCase()); and the server returns {"username":"asha"}.

terminal
$ Console
── what you'll see ──
Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'toUpperCase')

Myth vs fact

Myth

TypeScript code runs faster than JavaScript.

Fact

Types are erased, so the browser runs the same JavaScript either way. TypeScript makes code safer and easier to change, not faster.

Myth

If it compiles, it can't have type errors at run time.

Fact

Only if every value's type is honest. Outside data, any, and as casts can all lie to the checker. Validate data at the edges of your program.

Myth

TypeScript is a different language you must learn from scratch.

Fact

It is JavaScript with types on top. Everything from this course still applies; you add annotations gradually, and many types are inferred.

Myth

You need TypeScript to get type checking.

Fact

JSDoc comments plus // @ts-check give most of the same checking in plain .js files.

Pro corner

Extra depth for experienced readers. New to this? Skip it for now and come back later.

  • ▸

    TypeScript's type system is structural: two types are compatible if their shapes match, whatever their names. An object with name, price and qty is an Item even if it was never declared as one. Java and C# use nominal typing (names must match).

  • ▸

    Some TypeScript features are not just erasable annotations: enum, namespace and constructor parameter properties generate run-time code. TypeScript 5.8 added --erasableSyntaxOnly to forbid them, which matches what Node.js type stripping and the TC39 "type annotations" proposal (stage 1) can handle.

  • ▸

    strict: true turns on a family of checks; the most valuable is strictNullChecks, which makes null and undefined separate types, so document.querySelector("#x").textContent is an error ('...' is possibly 'null', TS18047) until you check the element exists. That alone prevents the most common JavaScript error, Cannot read properties of null.

  • ▸

    Microsoft is rewriting the TypeScript compiler in Go (the "native" port, planned as TypeScript 7) for roughly 10x faster type-checking in large projects. The language itself stays the same.

Remember this

  1. 1

    A type describes what kind of value something is: number, string, boolean, an array of numbers (number[]), or an object shape such as { name: string; age: number }. JavaScript has types too, but checks them only while running (it's dynamically typed), and often converts silently instead of complaining: "2" + 3 is "23". TypeScript (by Microsoft, first released in 2012) is a superset of JavaScript: every JavaScript program is valid TypeScript syntax, and TypeScript adds a type layer that is checked before running (static typing).

  2. 2

    You add types with annotations after a colon: let count: number = 0;, function add(a: number, b: number): number { return a + b; }. Often you don't need to write them, because TypeScript infers types from values: after let count = 0, it already knows count is a number. You name object shapes with an interface (interface User { name: string; age: number; }) or a type alias (type Id = string | number;). The | makes a union type: a value that may be one of several types, such as "light" | "dark".

  3. 3

    TypeScript files end in .ts. Browsers can't run them, because : number isn't JavaScript syntax. The TypeScript compiler, tsc, does two separate jobs: it type-checks the code and reports errors such as TS2345: Argument of type 'string' is not assignable to parameter of type 'number', and it emits JavaScript by erasing the types. Erasing means the types simply disappear; no checks are added to the output. Settings live in tsconfig.json; turn on "strict": true for new projects.

  4. 4

    Because types are erased, TypeScript can't check data that arrives while the program runs: JSON from an API, a value from localStorage, text from an <input>. If you write const user: User = await res.json(), TypeScript believes you, even if the server sent something else. Check outside data with real JavaScript (a type guard: a function that tests the shape) or a validation library such as Zod. Also avoid any, which turns checking off for that value; prefer unknown, which forces you to check before use.

  5. 5

    In practice the type-check and the JavaScript output are often done by different tools. Vite and esbuild strip types quickly without checking them (Topic 12.4), and your editor plus tsc --noEmit do the checking. Recent Node.js versions (22.18 and later) can run .ts files directly by stripping types, as long as the file only uses erasable syntax (annotations, interfaces, type aliases, but not enum). The editor is where TypeScript shines most: autocomplete, inline errors as you type and safe renames across a project.

  6. 6

    You can also get type checking without .ts files: write types in JSDoc comments (/** @param {number} price */) and put // @ts-check at the top of a .js file. The file stays plain JavaScript that runs anywhere, and the editor checks it with the TypeScript engine. Some libraries, such as Svelte's source, use this approach.

Explain it without notes

01

What is TypeScript, and how does it relate to JavaScript?

02

What does it mean that TypeScript types are erased, and what are the consequences?

03

What is the difference between any and unknown?

04

In a Vite + TypeScript project, which tool checks types and which produces the JavaScript?

Practice

01

Rewrite in JavaScript (types removed): function greet(name: string, times: number = 1): string { return ("Hi " + name + "! ").repeat(times).trim(); }, then log greet("Asha", 2).

02

Write a type guard isProduct(value) that accepts objects with a string title and a positive number price. Test it on three values.

03

Add JSDoc types to function area(width, height) { return width * height; } so // @ts-check would reject area("3", 4).

Trade-offs

  • ↔

    TypeScript catches whole classes of bugs early and makes large codebases easier to change, at the cost of a build or check step, a learning curve and occasionally fighting complex types.

  • ↔

    .ts files give the full language; JSDoc with // @ts-check needs no build step but is wordier and supports advanced types less comfortably.

Done when you can

  • I can explain static versus dynamic typing and what TypeScript adds to JavaScript.

  • I can write annotations, an interface and a union type, and I know types are often inferred.

  • I can run tsc --noEmit and read an error with its code, position and message.

  • I know types are erased, so I validate outside data with type guards.

  • I can use JSDoc and // @ts-check to type-check plain JavaScript.