After this lesson, you will be able to: Write TypeScript that uses primitive types, arrays, objects, interfaces, type aliases, union types, and function signatures; configure a tsconfig.json with strict mode; and explain why TypeScript is the professional default for new web projects in 2025.
TypeScript is JavaScript with a type checker layered on top. Microsoft built it in 2012; it's now the default for new web codebases at Google, Meta, Microsoft, Stripe, Vercel, and almost every modern startup. Every serious React, Next.js, and Node.js codebase you'll see at work in 2025 is written in TypeScript. This lesson takes you from never having written a type annotation to fluent in the everyday TypeScript surface you'll meet in real code. You won't be a TypeScript expert in 50 minutes, but you will be able to read it, modify it, and start writing it without panic.
JavaScript has no type checking. `"5" * 2` is `10`. `[] + []` is `""`. `user.naem` (typo) is `undefined`, no error, until your page crashes in production. Every reasonably-sized JS codebase eventually accumulates dozens of these bugs. TypeScript catches them at the moment you type them. The compiler reads your code, infers what types each variable should be, and tells you when you're using a value the wrong way — before the code ever runs. Three concrete payoffs: (1) refactors become safe (rename a field, the compiler flags every reference that needs updating); (2) IDE autocomplete actually knows what you can call on a value; (3) function signatures double as documentation. The cost: a build step, slightly more verbose code, and a learning curve measured in days, not months.
In 2025, the question is no longer "should I learn TypeScript?" It's table stakes. Stripe rewrote its frontend in TS. Microsoft built VS Code in it. Vercel ships Next.js docs telling you to use it. New React tutorials default to it. JetBrains and StackOverflow surveys consistently put TS in the top 5 most-loved languages. The hiring filter: junior developer job postings now routinely list TypeScript as required, not nice-to-have. A candidate who only writes JS is at an immediate disadvantage versus one who can sit down in a TS codebase on day one.
The function below adds two numbers. In JavaScript, calling it with a string would silently concatenate. In TypeScript, the compiler refuses to ship.
// JavaScript — silently wrongfunction add(a, b) {return a + b;}add(2, "3"); // "23" — probably not what you wanted// TypeScript — caught at compile timefunction addTs(a: number, b: number): number {return a + b;}addTs(2, "3");// Error: Argument of type 'string' is not assignable to parameter of type 'number'.// The bug never makes it to the browser.
Install TypeScript locally per project. The `tsconfig.json` controls how strict the checker is. For new code, always start with `strict: true` — anything less and you're paying for the build step without getting most of the value.
# Installnpm install -D typescript# Create tsconfig.json with sensible defaultsnpx tsc --init# Edit tsconfig.json — turn strict mode ON (default in newer scaffolds){"compilerOptions": {"target": "ES2022","module": "ESNext","strict": true, // CRITICAL — enables noImplicitAny + strictNullChecks + more"esModuleInterop": true,"skipLibCheck": true,"jsx": "preserve" // when using with React/Next.js}}# Type-check (no emit — Next.js/Vite usually transpile separately)npx tsc --noEmit
TypeScript can infer most types from context. Annotate function parameters and exported declarations; let inference handle the rest.
// Explicit annotationsconst name: string = "Alex";const age: number = 17;const isStudent: boolean = true;// Type inference — TypeScript knows these without you saying soconst greeting = "Hello"; // inferred as stringconst count = 0; // inferred as numberconst hobbies = ["code", "music"]; // inferred as string[]// null and undefined are distinct types in TSlet maybeName: string | null = null;maybeName = "Alex"; // OKmaybeName = 42; // Error: Type 'number' not assignable// 'any' is the escape hatch — disables type checking for that valuelet anything: any = 5;anything = "now a string"; // no error, but no protection either// Use 'unknown' instead when you genuinely don't know the shape yet:let unknownVal: unknown = JSON.parse(input);if (typeof unknownVal === "string") {console.log(unknownVal.toUpperCase()); // narrowed to string, safe}
Real code is mostly arrays and objects. TS gives you precise ways to describe both.
// Arraysconst names: string[] = ["Alex", "Sam"];const scores: Array<number> = [95, 88, 72]; // same as number[]// Tuples — fixed-length, position-typed arraysconst coord: [number, number] = [40.7, -74.0];const pair: [string, number] = ["age", 17];// Object types — inlinefunction printUser(user: { name: string; age: number }) {console.log(`${user.name} is ${user.age}`);}printUser({ name: "Alex", age: 17 });// Optional propertiesfunction render(user: { name: string; nickname?: string }) {return user.nickname ?? user.name;}
When the same object shape appears in multiple places, give it a name. `interface` and `type` both work for object shapes; `type` is more flexible (unions, primitives), `interface` extends more naturally.
// Interface — most common for object shapesinterface User {id: string;name: string;email: string;isAdmin?: boolean; // optional}function sendEmail(user: User, subject: string) {console.log(`To ${user.email}: ${subject}`);}// Type alias — works for objects too, AND for unions/primitivestype Status = "PENDING" | "CONFIRMED" | "CANCELLED";function setStatus(orderId: string, status: Status) {// status is constrained to those three literal strings}setStatus("o-1", "CONFIRMED");setStatus("o-1", "WAITING"); // Error: not assignable to type Status// Extending interfacesinterface AdminUser extends User {permissions: string[];}
Annotate parameters always; annotate return types when the function is exported or when you want to enforce a contract explicitly.
// Explicit parameter and return typesfunction add(a: number, b: number): number {return a + b;}// Optional + default parametersfunction greet(name: string, greeting: string = "Hello"): string {return `${greeting}, ${name}`;}// Arrow function with typesconst multiply = (a: number, b: number): number => a * b;// Function type as a parameterfunction repeat(fn: (x: number) => number, times: number) {let value = 1;for (let i = 0; i < times; i++) value = fn(value);return value;}// async functions return Promise<T>async function fetchUser(id: string): Promise<User> {const response = await fetch(`/api/users/${id}`);return await response.json() as User;}
The starter is a small JavaScript module. Convert it to TypeScript: annotate every parameter, give the `User` shape an interface, narrow `status` to a string-literal union, and have `formatUser` declare its return type. Required patterns check for the interface, the literal union, and a return-type annotation.
Pick the most accurate answer for a 2025 codebase.
Sign in and purchase access to unlock this lesson.