TypeScript has become the standard for modern JavaScript development. In 2026, over 80% of new JavaScript projects use TypeScript. This complete guide covers everything from basic types to advanced generics, decorators, and production patterns.
📋 Table of Contents
Why TypeScript in 2026?
JavaScript’s dynamic nature causes runtime errors that TypeScript catches at compile time. The benefits are clear:
- Catch bugs early — type errors at compile time, not in production
- Better tooling — autocomplete, refactoring, and navigation in VS Code
- Self-documenting code — types serve as inline documentation
- Large-scale confidence — safe refactoring across thousands of files
Installation and Setup
Install TypeScript globally or as a dev dependency:
# Install TypeScript
npm install -g typescript
# Or as dev dependency
npm install --save-dev typescript
# Check version
tsc --version
# Initialize tsconfig
tsc --init
tsconfig.json Best Practices
A production-ready tsconfig.json for 2026:
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "bundler",
"strict": true,
"noUncheckedIndexedAccess": true,
"exactOptionalPropertyTypes": true,
"noImplicitOverride": true,
"lib": ["ES2022", "DOM"],
"outDir": "./dist",
"rootDir": "./src",
"declaration": true,
"sourceMap": true,
"esModuleInterop": true,
"skipLibCheck": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}
Basic Types
TypeScript’s type system starts with primitives and builds up:
// Primitives
const name: string = "Alice";
const age: number = 30;
const active: boolean = true;
const nothing: null = null;
const unknown: undefined = undefined;
// Arrays
const scores: number[] = [95, 87, 92];
const tags: Array<string> = ["ts", "js", "node"];
// Tuples (fixed-length arrays with known types)
const point: [number, number] = [10, 20];
const entry: [string, number] = ["age", 30];
// Union types
let id: string | number = "user_123";
id = 456; // also valid
// Literal types
type Direction = "north" | "south" | "east" | "west";
const dir: Direction = "north";
// any, unknown, never
const userInput: unknown = getUserInput();
if (typeof userInput === "string") {
console.log(userInput.toUpperCase()); // type narrowed to string
}
Interfaces vs Types
Both define object shapes, but with key differences:
// Interface — can be merged (declaration merging)
interface User {
id: number;
name: string;
email?: string; // optional
}
// Extend interface
interface AdminUser extends User {
role: "admin" | "superadmin";
permissions: string[];
}
// Type alias — more flexible, supports unions and intersections
type ApiResponse<T> = {
data: T;
error: string | null;
status: number;
};
// Intersection type
type AuthUser = User & { token: string };
// Prefer interface for object shapes, type for unions/intersections
const user: AdminUser = {
id: 1,
name: "Alice",
role: "admin",
permissions: ["read", "write"]
};
Generics
Generics make code reusable while staying type-safe:
// Generic function
function first<T>(arr: T[]): T | undefined {
return arr[0];
}
const num = first([1, 2, 3]); // type: number | undefined
const str = first(["a", "b"]); // type: string | undefined
// Generic with constraints
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const user = { id: 1, name: "Alice", age: 30 };
const name = getProperty(user, "name"); // type: string
const id = getProperty(user, "id"); // type: number
// Generic interface
interface Repository<T> {
findById(id: number): Promise<T | null>;
findAll(): Promise<T[]>;
create(item: Omit<T, "id">): Promise<T>;
update(id: number, item: Partial<T>): Promise<T>;
delete(id: number): Promise<void>;
}
// Generic class
class Stack<T> {
private items: T[] = [];
push(item: T): void { this.items.push(item); }
pop(): T | undefined { return this.items.pop(); }
peek(): T | undefined { return this.items[this.items.length - 1]; }
isEmpty(): boolean { return this.items.length === 0; }
}
const stack = new Stack<number>();
stack.push(1);
stack.push(2);
console.log(stack.pop()); // 2
Utility Types
TypeScript ships with powerful built-in utility types:
interface Product {
id: number;
name: string;
price: number;
description: string;
inStock: boolean;
}
// Partial — all fields optional
type ProductDraft = Partial<Product>;
// Required — all fields required
type FullProduct = Required<Product>;
// Pick — select specific fields
type ProductSummary = Pick<Product, "id" | "name" | "price">;
// Omit — exclude fields
type NewProduct = Omit<Product, "id">;
// Readonly — prevent mutation
type ImmutableProduct = Readonly<Product>;
// Record — map type
type CategoryMap = Record<string, Product[]>;
// ReturnType — extract function return type
async function fetchUser() {
return { id: 1, name: "Alice", email: "alice@example.com" };
}
type User = Awaited<ReturnType<typeof fetchUser>>;
// Parameters — extract function parameters
function createOrder(userId: number, items: string[], total: number) {}
type OrderParams = Parameters<typeof createOrder>;
Advanced Patterns: Conditional Types
// Conditional type
type IsArray<T> = T extends any[] ? true : false;
type A = IsArray<string[]>; // true
type B = IsArray<string>; // false
// Infer keyword
type UnpackArray<T> = T extends (infer U)[] ? U : T;
type Unpacked = UnpackArray<string[]>; // string
type NotArray = UnpackArray<number>; // number
// Mapped types
type Optional<T> = {
[K in keyof T]?: T[K];
};
// Template literal types (TypeScript 4.1+)
type EventName<T extends string> = `on${Capitalize<T>}`;
type ClickEvent = EventName<"click">; // "onClick"
type FocusEvent = EventName<"focus">; // "onFocus"
// Discriminated unions
type Result<T> =
| { success: true; data: T }
| { success: false; error: string };
function processResult<T>(result: Result<T>): T {
if (result.success) {
return result.data; // TypeScript knows this is T
}
throw new Error(result.error); // TypeScript knows error is string
}
TypeScript with React
import React, { useState, useCallback, FC } from "react";
interface ButtonProps {
label: string;
onClick: () => void;
variant?: "primary" | "secondary" | "danger";
disabled?: boolean;
children?: React.ReactNode;
}
const Button: FC<ButtonProps> = ({
label,
onClick,
variant = "primary",
disabled = false,
children
}) => {
return (
<button
className={`btn btn-${variant}`}
onClick={onClick}
disabled={disabled}
>
{children ?? label}
</button>
);
};
// Generic component
interface ListProps<T> {
items: T[];
renderItem: (item: T, index: number) => React.ReactNode;
keyExtractor: (item: T) => string | number;
}
function List<T>({ items, renderItem, keyExtractor }: ListProps<T>) {
return (
<ul>
{items.map((item, index) => (
<li key={keyExtractor(item)}>{renderItem(item, index)}</li>
))}
</ul>
);
}
// Custom hook with types
function useLocalStorage<T>(key: string, initialValue: T) {
const [value, setValue] = useState<T>(() => {
const stored = localStorage.getItem(key);
return stored ? JSON.parse(stored) : initialValue;
});
const setStoredValue = useCallback((newValue: T) => {
setValue(newValue);
localStorage.setItem(key, JSON.stringify(newValue));
}, [key]);
return [value, setStoredValue] as const;
}
TypeScript Decorators (2026)
TypeScript 5.0+ supports Stage 3 decorators:
// Class decorator
function singleton<T extends { new(...args: any[]): {} }>(constructor: T) {
let instance: T;
return class extends constructor {
constructor(...args: any[]) {
if (instance) return instance;
super(...args);
instance = this as any;
}
};
}
// Method decorator — log execution time
function measure(_target: any, key: string, descriptor: PropertyDescriptor) {
const original = descriptor.value;
descriptor.value = async function (...args: any[]) {
const start = performance.now();
const result = await original.apply(this, args);
const duration = performance.now() - start;
console.log(`${key} took ${duration.toFixed(2)}ms`);
return result;
};
return descriptor;
}
class UserService {
@measure
async fetchUsers(): Promise<User[]> {
const r = await fetch("/api/users");
return r.json();
}
}
Strict Mode Checklist
Enable these compiler options for maximum type safety:
strict: true— enables all strict checksnoUncheckedIndexedAccess: true— array access returnsT | undefinedexactOptionalPropertyTypes: true—undefinednot assignable to optionalnoImplicitOverride: true— must useoverridekeywordnoPropertyAccessFromIndexSignature: true— use bracket notation for index sigs
TypeScript Tooling in 2026
- Biome — fastest linter + formatter, TypeScript-native
- tsx — run TypeScript files directly without compiling
- tsup — zero-config bundler for TypeScript libraries
- Vitest — TypeScript-first testing with native ESM
- tRPC — end-to-end type safety for full-stack apps
Key Takeaways
TypeScript in 2026 is mature, fast, and essential for professional JavaScript development. Start with strict: true, use interfaces for object shapes, lean on utility types, and leverage generics for reusable code. The tooling ecosystem has never been better.
📚 You might also like
🔗 Share this article




✍️ Leave a Comment