TypeScript Reference: Types, Generics, Narrowing, tsconfig & Advanced Patterns
TypeScript is JavaScript with static types. It compiles to plain JavaScript and runs anywhere JS runs. The key skills: understanding the type system (interfaces vs types, generics, utility types, narrowing) and configuring tsconfig.json correctly for your environment.
1. Types, Interfaces & Union Types
Core type system — primitives, objects, unions, intersections
// Primitives:
let name: string = "Alice";
let age: number = 30;
let active: boolean = true;
let data: unknown = fetchData(); // unknown forces type checking before use
let value: any = "anything"; // any = escape hatch (avoid in new code)
let nothing: void = undefined; // function return type for no value
let impossible: never; // function that never returns (throws/infinite loop)
// Arrays and tuples:
let names: string[] = ["Alice", "Bob"];
let pair: [string, number] = ["Alice", 30]; // tuple: fixed-length, typed positions
// Object types (interface):
interface User {
id: number;
name: string;
email?: string; // optional property
readonly createdAt: Date; // can't be reassigned after creation
}
// Object types (type alias — more flexible than interface for complex types):
type Point = { x: number; y: number };
type ID = string | number; // union type
// Union and intersection:
type Status = "pending" | "active" | "inactive"; // string literal union
type AdminUser = User & { role: "admin"; permissions: string[] }; // intersection
// Discriminated union (exhaustive pattern matching):
type Shape =
| { kind: "circle"; radius: number }
| { kind: "rectangle"; width: number; height: number };
function area(shape: Shape): number {
switch (shape.kind) {
case "circle": return Math.PI * shape.radius ** 2;
case "rectangle": return shape.width * shape.height;
// TypeScript warns if you miss a case (exhaustiveness check)
}
}
2. Generics
Type-safe reusable functions, classes, and utility types
// Generic function: function first(arr: T[]): T | undefined { return arr[0]; } const n = first([1, 2, 3]); // TypeScript infers T = number // Generic with constraint (only objects with a 'name' field): function greet (obj: T): string { return `Hello, ${obj.name}`; } // Generic interface: interface ApiResponse { data: T; status: number; message: string; } type UserResponse = ApiResponse ; type ListResponse = ApiResponse ; // Built-in utility types (use these — don't reinvent them): type PartialUser = Partial ; // all fields optional type RequiredUser = Required ; // all fields required type ReadonlyUser = Readonly ; // all fields readonly type UserKeys = keyof User; // "id" | "name" | "email" | "createdAt" type PickedUser = Pick ; // subset of fields type OmittedUser = Omit ; // remove fields type RecordType = Record ; // { [key: string]: number } type NonNullable = T extends null | undefined ? never : T; type ReturnType ; // extract return type of a function type Parameters ; // extract parameter types tuple
3. Type Narrowing & Guards
typeof, instanceof, in, custom type guards, and assertion functions
// TypeScript narrows types automatically in branches:
function process(value: string | number) {
if (typeof value === "string") {
return value.toUpperCase(); // TypeScript knows it's string here
}
return value * 2; // TypeScript knows it's number here
}
// instanceof narrowing:
function formatError(error: unknown) {
if (error instanceof Error) {
return error.message; // TypeScript knows it's Error here
}
return String(error);
}
// 'in' narrowing (check if property exists):
type Cat = { meow(): void };
type Dog = { bark(): void };
function makeSound(animal: Cat | Dog) {
if ("meow" in animal) {
animal.meow(); // Cat
} else {
animal.bark(); // Dog
}
}
// Custom type guard (predicate function):
function isUser(obj: unknown): obj is User {
return typeof obj === "object" && obj !== null &&
"id" in obj && "name" in obj;
}
// Usage:
const data: unknown = JSON.parse(response);
if (isUser(data)) {
console.log(data.name); // TypeScript knows it's User here
}
// Non-null assertion (use sparingly — bypasses null check):
const el = document.getElementById("app")!; // ! = "I know this is not null"
4. tsconfig.json — Key Options
Strict mode, module resolution, paths, and environment targets
// tsconfig.json — recommended base:
{
"compilerOptions": {
"target": "ES2022", // output JS version (ES2022 for Node.js 18+)
"module": "NodeNext", // module system: NodeNext (Node 18+), ESNext, CommonJS
"moduleResolution": "NodeNext", // MUST match 'module' for Node.js ESM
"lib": ["ES2022"], // type definitions to include
"outDir": "./dist", // where compiled JS goes
"rootDir": "./src", // source directory
"strict": true, // ALWAYS enable (enables 8 checks at once)
"noUncheckedIndexedAccess": true, // arr[0] is T | undefined, not T (catches OOB)
"exactOptionalPropertyTypes": true, // {a?: string} means string, not string|undefined
"noImplicitReturns": true, // function must return in all code paths
"noFallthroughCasesInSwitch": true, // switch case fallthrough is an error
// Path aliases (avoid ../../.. imports):
"baseUrl": ".",
"paths": {
"@/*": ["src/*"], // import '@/utils/db' → src/utils/db
"@shared/*": ["src/shared/*"]
},
// Declaration files (for publishing a library):
"declaration": true,
"declarationMap": true,
"sourceMap": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}
// For React projects (Vite):
// "jsx": "react-jsx" (not "react" — avoids importing React in every file)
// "lib": ["ES2022", "DOM", "DOM.Iterable"]
// "module": "ESNext", "moduleResolution": "Bundler"
// Check types without emitting:
// tsc --noEmit (use in CI to type-check without building)
5. Advanced Patterns
Mapped types, template literals, conditional types, and declaration merging
// Mapped types (transform every property): type Optional= { [K in keyof T]?: T[K] }; // same as Partial type Nullable = { [K in keyof T]: T[K] | null }; type Getters = { [K in keyof T as `get${Capitalize }`]: () => T[K] }; // Getters<{name: string}> → {getName: () => string} // Template literal types (string manipulation at type level): type EventName = "click" | "focus" | "blur"; type Handler = `on${Capitalize }`; // "onClick" | "onFocus" | "onBlur" // Conditional types: type IsArray = T extends any[] ? true : false; type Flatten = T extends Array ? U : T; // Flatten = string; Flatten = string // Infer keyword (extract types from complex shapes): type UnpackPromise = T extends Promise ? U : T; // UnpackPromise > = User // Declaration merging (augmenting interfaces across files): // Useful for extending global types (e.g. Express Request): declare namespace Express { interface Request { user?: User; // add user property to all Express requests } } // Const assertions (literal type preservation): const config = { endpoint: "https://api.example.com", timeout: 5000 } as const; // config.endpoint is "https://api.example.com" (not string) // config is Readonly — no reassignment allowed // Satisfies operator (TS 4.9+): validate type without widening: const palette = { red: [255, 0, 0], green: "#00ff00" } satisfies Record ; // palette.red is number[], not string | number[] (keeps specifics)
Track TypeScript and Node.js releases.
ReleaseRun monitors Node.js, TypeScript, React, and 13+ technologies.
Related: JavaScript ES6+ Reference | Node.js Reference | React Reference | TypeScript EOL Tracker
🔍 Free tool: npm Package Health Checker — check any npm package for outdated TypeScript types, EOL status, and known vulnerabilities instantly.
📦 Free tool: package.json Health Checker — paste your entire package.json and get A–F health grades for every dependency at once, including TypeScript, ts-node, and @types/* packages.
Founded
2023 in London, UK
Contact
hello@releaserun.com