TypeScript
Status: đźź© COMPLETE Last updated: 2026-06-20 Plain-English tagline: JavaScript with types. Same language, plus a compiler that catches an entire class of bugs before you run the code.
In plain English
TypeScript is JavaScript with optional type annotations and a compiler that checks them. You write code that looks almost exactly like JavaScript, but with extra little tags saying what type each value is supposed to be. The TypeScript compiler (tsc) reads your code and yells when something doesn’t match.
function add(a: number, b: number): number {
return a + b;
}
add(2, 3); // âś“ ok
add(2, "three"); // âś— TypeScript error: argument of type 'string' is not assignable to parameter of type 'number'The compiler catches the second call before you run the code. The bug never makes it to production. Multiply this by an entire codebase and you eliminate a huge class of subtle errors that would otherwise show up as runtime crashes.
TypeScript is developed by Microsoft (since 2012). It’s free and open source. The output is just JavaScript — the types disappear at compile time. Modern Next.js, React, Node.js projects all use TypeScript by default in 2026.
Why it matters
- Catches bugs before runtime. “I passed a string where a number was expected” — caught at edit time, not in production.
- Better autocomplete. Your editor (VS Code especially) knows the shape of everything, suggests methods, surfaces docs inline.
- Refactoring is safe. Rename a property → every usage updates correctly, mistakes flagged immediately.
- Self-documenting code. The types ARE the documentation for what a function takes and returns.
- Faster onboarding. New people (or future-you) can read the types and understand what’s expected.
- Modern tooling assumes it. Most React/Next.js tutorials, examples, and libraries are TypeScript-first now.
The cost is some upfront learning and slightly more typing. Almost every project that’s tried TypeScript and gone back to plain JS regrets it.
A 5-minute tour
Primitive types
const age: number = 99;
const name: string = "George";
const isActive: boolean = true;
const value: null = null;
const nothing: undefined = undefined;For most assignments you don’t write the type — TypeScript infers it. The above are explicit for clarity.
Arrays
const numbers: number[] = [1, 2, 3];
const names: string[] = ["Alice", "Bob"];
// equivalent:
const numbers2: Array<number> = [1, 2, 3];Objects
const user: { name: string; age: number } = {
name: "George",
age: 99
};Usually you give the shape a name (called an interface or type):
interface User {
name: string;
age: number;
email?: string; // optional
}
const u: User = { name: "George", age: 99 };interface and type do almost the same thing. Differences are subtle. Most teams pick one and stick with it. For object shapes, interface is conventional; for everything else, type.
Functions
function add(a: number, b: number): number {
return a + b;
}
// Arrow function with type
const greet = (name: string): string => `Hello, ${name}`;
// Optional parameter
const greet2 = (name: string, greeting?: string): string =>
`${greeting ?? "Hello"}, ${name}`;The return type after : is often optional (TypeScript infers it). Annotating it explicitly is good practice for public APIs.
Union types
A value that can be one of several types:
type Status = "loading" | "ready" | "error";
let status: Status = "loading";
status = "ready"; // âś“
status = "done"; // âś— error
let id: string | number;
id = "abc";
id = 123;
id = true; // âś— errorUnions are everywhere. string | number | null, "on" | "off", User | null.
Generics — parameterized types
A function or type that works with any type, while preserving type info:
function identity<T>(value: T): T {
return value;
}
identity(42); // T inferred as number
identity("hello"); // T inferred as string
// In React, props are generic
function List<T>({ items }: { items: T[] }) {
return <ul>{items.map((item, i) => <li key={i}>{String(item)}</li>)}</ul>;
}Generics are why your IDE knows useState<number>(0) returns [number, Dispatch<SetStateAction<number>>]. They’re advanced but inescapable in real TypeScript codebases.
any — the escape hatch
let x: any = 5;
x = "hello"; // ok
x.foo.bar.baz; // ok at compile time, may crash at runtimeany turns off type checking for that value. Use it sparingly — it’s an admission that you’re giving up TypeScript’s benefits.
For “I don’t know the type but I want to be safe,” use unknown instead. It forces you to narrow the type before using it.
Type assertion
const element = document.getElementById("root") as HTMLDivElement;“Trust me, this is the type I say it is.” Useful when you know more than the compiler. Don’t lie — it’ll crash at runtime if you’re wrong.
Type narrowing
TypeScript follows control flow:
function process(value: string | number) {
if (typeof value === "string") {
value.toUpperCase(); // TypeScript knows it's a string here
} else {
value.toFixed(2); // TypeScript knows it's a number here
}
}Narrowing works with typeof, instanceof, in, equality checks, and custom type guards. Powerful and intuitive.
The TypeScript compiler — tsc
npx tsc # compile the project (uses tsconfig.json)
npx tsc --noEmit # type-check only, no JS output
npx tsc --watch # watch modeIn Next.js, the framework runs TypeScript checks during next build. You usually don’t run tsc directly except for typecheck-only checks in CI.
The config lives in tsconfig.json:
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"strict": true,
"noUncheckedIndexedAccess": true,
"jsx": "preserve",
"esModuleInterop": true,
"skipLibCheck": true,
"moduleResolution": "bundler",
"paths": {
"@/*": ["./*"]
}
},
"include": ["**/*.ts", "**/*.tsx"],
"exclude": ["node_modules"]
}strict: true enables all the strict checks. For new projects, always start strict.
Type definitions for libraries
JavaScript libraries don’t have types built in. The community publishes types separately:
npm install --save-dev @types/node @types/reactModern libraries (most of them now) include types directly — you install the package and the types come with it. Older or pure-JS libraries need @types/... companion packages.
A concrete example: a typed React component
import { useState } from "react";
interface User {
id: string;
name: string;
email: string;
}
interface UserCardProps {
user: User;
onSelect?: (user: User) => void;
}
export function UserCard({ user, onSelect }: UserCardProps) {
const [isExpanded, setIsExpanded] = useState<boolean>(false);
return (
<article onClick={() => onSelect?.(user)}>
<h2>{user.name}</h2>
<p>{user.email}</p>
<button onClick={(e) => { e.stopPropagation(); setIsExpanded(!isExpanded); }}>
{isExpanded ? "Collapse" : "Expand"}
</button>
</article>
);
}Everything is typed: the props, the state, the event handlers. If you try to pass a User without an email, or call onSelect with wrong arguments, TypeScript catches it at edit time. Your IDE autocompletes every property.
TypeScript at scale — patterns worth knowing
Discriminated unions
type Result<T> =
| { status: "success"; data: T }
| { status: "error"; error: string }
| { status: "loading" };
function render<T>(result: Result<T>) {
switch (result.status) {
case "success": return result.data; // TypeScript knows .data exists here
case "error": return result.error; // .error exists here
case "loading": return "loading...";
}
}Discriminated unions + switch statements are how you model state machines in TypeScript. Used heavily for React component states, API responses, form fields, etc.
Utility types
TypeScript has built-in helpers:
type UserSummary = Pick<User, "id" | "name">; // only id and name
type UserWithoutEmail = Omit<User, "email">; // everything except email
type PartialUser = Partial<User>; // all fields optional
type RequiredUser = Required<User>; // all fields required
type ReadonlyUser = Readonly<User>; // can't mutate
type UserKeys = keyof User; // "id" | "name" | "email"These compose beautifully. Read a few projects’ types and you’ll see them everywhere.
as const
const colors = ["red", "green", "blue"] as const;
// type is readonly ["red", "green", "blue"], not string[]
type Color = typeof colors[number]; // "red" | "green" | "blue"Useful for deriving union types from arrays of constants.
Common gotchas
-
anydefeats the purpose. Use sparingly. Treat eachanyas a TODO. -
unknownis the safer alternative. When you don’t know the type, useunknownand narrow before using. -
Type assertions can lie.
as Foodoesn’t actually convert — it just tells the compiler “trust me.” Wrong assertions crash at runtime. -
Optional chaining + nullish coalescing.
user?.email ?? "none"is the modern way to handle possibly-missing values. -
nullvsundefined. TypeScript distinguishes them (strict mode). Most APIs useundefinedfor “not set” andnullrarely. Pick a convention. -
strictNullCheckschanges everything. When on,nullandundefinedaren’t assignable to other types implicitly. Strict mode includes this. Always use strict mode. -
@ts-ignoreand@ts-expect-errorskip checks for a line. Use as last resort.expect-erroris better — it errors if the next line was actually fine. -
Type errors are sometimes long. TypeScript’s error messages for complex generic mismatches can be wall-of-text. Read them line by line; the actual problem is usually in the first or last few lines.
-
Excess property checks.
{ name: "x", extra: 1 }passed to a function expecting{ name: string }errors if it’s an object literal but not if it’s a variable. Subtle. -
Array.isArray()and union narrowing. UseArray.isArray()to narrowT | T[]types. -
Bracket access without
noUncheckedIndexedAccess.array[0]is typed asT(noundefined) by default. But the array might be empty. SetnoUncheckedIndexedAccess: trueto force handling. -
Editor red squiggles that don’t match the CLI. The TypeScript language server (in VS Code) and
tscshould match. If they don’t, you may have version mismatches. Use the workspace TypeScript version.
See also
- JavaScript 🟩 — the underlying language
- React 🟩 — almost always paired with TypeScript
- Next.js 🟩 🟦 — TS by default in new projects
- Type checking đźź©
- Linting đźź©
- Glossary: TypeScript, Interface, Generic
Sources
- TypeScript Handbook — canonical, free
- TypeScript Deep Dive (free) — deeper than the handbook
- Type Challenges — puzzles to level up type-fu
- TotalTypeScript — Matt Pocock’s courses (some free)