Frontend
Intermediate 16 min read Frontend

TypeScript

TypeScript adds static types, interfaces and generics to JavaScript and compiles to plain JS. Learn structural typing, the strict compiler, and how types turn runtime crashes into compile-time errors.

TypeScripttypesgenericsstrict modestructural typingtsc
Prerequisites: JavaScript Core
Introduction

What is this?

TypeScript is a typed superset of JavaScript that compiles to plain, standards-compliant JS. It uses structural typing — compatibility is based on shape, not on names — and a strict compiler flags mismatches before code ever runs. Types are erased at compile time, so they add zero runtime cost.

Motivation

Why do you need it?

  • The compiler catches null dereferences, misspelled properties and shape mismatches at build time instead of in production.
  • Editors surface autocomplete, rename refactors and inline type errors from the same type information.
  • Types double as living documentation for the contracts between teams and services.
  • Large refactors become mechanical and safe: the compiler lists every call site that must change.
  • The community ships types for virtually every npm package, so typed code integrates cleanly.
Think about it

Mental model

Imagine

Architectural blueprints

Think of

TypeScript types

Blueprints specify rooms, doors and wiring before construction so mistakes are caught on paper. Types specify the shape of data and functions so mistakes are caught at compile time, not by users.

Mechanics

How it works

  • tsc parses TypeScript source into an abstract syntax tree annotated with inferred types.
  • The type checker walks the tree, resolving each expression's type and reporting errors without emitting anything.
  • Structural typing compares object shapes: a type is assignable if it has every required member with a compatible type.
  • Generics abstract over types so one function can serve many shapes while preserving exact relationships.
  • The emitter strips type annotations and down-levels modern syntax to the configured ECMAScript target.

The TypeScript compile pipeline.

TypeScript source (.ts)
tsc parse
Type checking
Emit
Plain JavaScript
Source maps
Reference

Key commands

npm install --save-dev typescript

Add the TypeScript compiler to the project.

npx tsc --init

Generate a tsconfig.json with sensible defaults.

Created a new tsconfig.json with: ...
npx tsc --noEmit

Type-check the project without writing any output files.

npx tsc

Type-check and emit JavaScript to the configured outDir.

npx tsc --watch

Re-run the type checker whenever a file changes.

npx tsc --strict --noEmit src/index.ts

Type-check one file under the strictest settings.

Hands-on

Examples

A typed generic API client

A typed generic API client
interface User {
id: number;
name: string;
active: boolean;
}
async function fetchJson<T>(url: string): Promise<T> {
const response = await fetch(url);
if (!response.ok) {
throw new Error("Request failed: " + response.status);
}
return (await response.json()) as T;
}
function describeUsers(users: User[]): string {
const active = users.filter((u) => u.active).map((u) => u.name);
return active.length ? active.join(", ") : "No active users";
}
fetchJson<User[]>("https://api.example.com/users")
.then((users) => console.log(describeUsers(users)))
.catch((error) => console.error(error.message));

The User interface defines the shape the code relies on. fetchJson<T> keeps the return type generic so callers get precise types. describeUsers is statically checked: passing User[] is enforced, and any mismatch fails at compile time.

Real world

In production

  • Large codebases adopt TypeScript to keep refactoring safe; millions of repositories use it in production.
  • API clients are generated from OpenAPI specs as typed clients, eliminating guesswork at call sites.
  • Frontend state and domain models are typed once and shared between server and client.
  • Config files and CI scripts use TypeScript (with ts-node or Bun) for self-documenting automation.
Guidelines

Best practices

  • Enable strict mode and never turn it off to silence errors.
  • Prefer unions, interfaces and generics over any; treat any as a defect.
  • Use unknown instead of any for values whose type you must narrow at runtime.
  • Keep types near the data they describe and export them from domain modules.
  • Model impossible states with discriminated unions so exhaustive checks catch missing branches.
Watch out

Common mistakes

  • Skipping strict null checks, then crashing on null at runtime.
  • Spreading any through the codebase until the compiler cannot help anymore.
  • Writing verbose, redundant type annotations everywhere instead of letting inference work.
  • Ignoring ts-ignore comments that hide real errors and rot over time.
Root cause analysis

Common issues

Error
Property 'x' does not exist on type '{}'
Root cause

An object literal or API response was not given a type, so it inferred to an empty shape.

Fix

Define an interface and annotate the variable or function return type.

Error
Cannot find module './types' or its corresponding type declarations
Root cause

The path is wrong, or the module is JS without a declaration file.

Fix

Fix the import path, or add a declaration file (module.d.ts) describing the module.

Error
Type 'string | undefined' is not assignable to type 'string'
Root cause

Strict null checks flag a possibly-undefined value.

Fix

Guard with an if check, use a fallback with ??, or restructure the type to remove undefined.

When things break

Debugging tips

  • Run npx tsc --noEmit after every change so the compiler is the first test.
  • Hover any symbol in your editor to inspect its inferred type, then compare with what you intended.
  • Use unknown plus type guards (typeof, Array.isArray, custom is) to narrow safely.
  • Temporarily add an extra variable with an explicit annotation to expose where a mismatch begins.
Interviews

Interview checkpoint

Practice

Hands-on lab

Type a Data Fetching Utility
30 min · intermediate
In progress
Objectives
  • Configure a strict tsconfig
  • Define interfaces for API payloads
  • Write a generic request helper
  • Verify with tsc --noEmit

Type a Data Fetching Utility

30 min · intermediate

0/5 steps
0%
sandbox · Linux sandbox
$
Try these commands
Test yourself

Quick quiz

0/3
1

Which tsconfig option enables the strictest null and assignment checks?

2

Type annotations add runtime overhead in the compiled JavaScript.

3

Which utility type makes every property of T optional?

Quick reference

Cheat sheet

Core types
string | numberUnion of allowed types
interface User { name: string }Object shape definition
function f<T>(x: T): TGeneric function
const v: unknown = ...Unknown that must be narrowed
readonlyImmutable property
Utility types
Partial<T>All properties optional
Pick<T, 'id' | 'name'>Subset of properties
Record<K, V>Map of keys to values
ReturnType<typeof fn>Infer a function return type
Go deeper

Official documentation