Loading...
Loading...
TypeScript fundamentals, object typing, functions, generics, and practical TS workflows. Use this page as a focused learning resource for typescript concepts, interview preparation, and practical revision.
TypeScript is a strongly-typed programming language that builds upon JavaScript by adding static type definitions. Developed and maintained by Microsoft, TypeScript has grown from a niche tool for large codebases into the default choice for professional JavaScript development worldwide. In 2026, TypeScript powers some of the most critical software systems on the planet - from major cloud platforms and financial applications to mobile apps, developer tools, and AI-powered services.
At its core, TypeScript is a superset of JavaScript: every valid JavaScript program is also valid TypeScript. TypeScript adds an optional type system, modern language features (that compile to older JavaScript), and powerful tooling support. When you compile TypeScript, it produces plain JavaScript that runs in any browser, Node.js environment, or other JavaScript runtime. TypeScript itself never runs directly - it is purely a compile-time tool that helps you write better JavaScript.
The explosion in TypeScript adoption over the past decade is one of the most significant trends in software engineering. According to major developer surveys in 2026, TypeScript adoption among professional development teams has crossed 85%, and it has been ranked among the most loved and most used programming languages for five consecutive years. Most importantly, developers who learn TypeScript rarely want to go back to plain JavaScript.
Several powerful forces keep TypeScript at the center of modern development. First, the type system catches entire categories of bugs before code ever runs - null pointer exceptions, property access on undefined, incorrect function signatures, and mismatched data shapes are caught at compile time rather than discovered by users at runtime.
Second, TypeScript dramatically improves developer tooling. Because the type system knows exactly what shape every value has, IDEs can provide precise autocompletion, instant error highlighting, reliable refactoring support, inline documentation, and accurate "Go to Definition" navigation. These tools make developers significantly more productive and dramatically reduce the time spent debugging.
Third, TypeScript makes large codebases maintainable. When a team needs to change a function signature, TypeScript immediately shows every call site that needs to be updated. When adding a new required field to a type, TypeScript flags every place that creates objects of that type and is missing the new field. This makes large-scale refactoring safe and fast.
📊 TypeScript in 2026
TypeScript is now the dominant language for frontend development (used in over 80% of React projects), widely adopted for backend Node.js development, the primary language for Angular and Deno applications, and increasingly used for mobile development via React Native. The npm registry shows TypeScript type definitions available for over 95% of the most popular packages.
A common misconception is that TypeScript and JavaScript are competing languages. They are not - TypeScript is JavaScript with a type layer on top. Think of TypeScript as JavaScript with a powerful pre-flight checklist that catches mistakes before you deploy. You still write familiar JavaScript logic - objects, arrays, functions, classes, async/await - but with the added safety of types that verify your code's correctness at development time.
This guide covers TypeScript from the ground up through advanced patterns used in production systems. Whether you are adding TypeScript to an existing JavaScript project, starting a new project with TypeScript from scratch, or deepening your understanding of the type system's more powerful features, this guide provides the comprehensive reference you need.
TypeScript was created by Anders Hejlsberg, the lead architect of C# and the designer of Turbo Pascal and Delphi. Microsoft announced it publicly on October 1, 2012, after two years of internal development. Version 0.8 was the first public release, introducing basic type annotations, interfaces, classes, and modules to JavaScript.
In its early years, TypeScript faced skepticism. JavaScript developers were accustomed to the language's dynamic nature, and many viewed static typing as an unnecessary burden. However, a pivotal moment came in 2015 when the Angular team (then developing Angular 2, a complete rewrite of AngularJS) announced they would build Angular 2 entirely in TypeScript. This decision, made by one of the most widely used JavaScript frameworks, lent enormous credibility to TypeScript and brought it to the attention of millions of developers.
TypeScript 1.x introduced the foundational features: type inference (so you don't need to annotate everything explicitly), structural typing (types are compatible if they have the same shape, regardless of name), union types, intersection types, and generics. Thetsconfig.jsonconfiguration file was introduced in TypeScript 1.5, making it practical to use TypeScript in real projects.
TypeScript 2.x was a major maturation phase. Non-nullable types (thestrictNullChecksoption) made it possible to express that a value might benullorundefinedand forced developers to handle those cases explicitly - eliminating entire classes of null reference errors. Mapped types, conditional types, and template literal types progressively extended the type system's power to express complex relationships between types.
TypeScript 3.x continued with important quality-of-life improvements: rest and spread for generic types, theunknowntype (a safe alternative toany), and optional chaining (?.) and nullish coalescing (??) operator support before they were finalized in JavaScript.
TypeScript 4.x brought variadic tuple types, template literal types, recursive conditional types, and significant improvements to the control flow analysis that underlies type narrowing. TypeScript 5.0 (2023) was the first major version bump in a decade, bringing the new decorator standard implementation,consttype parameters, resolution customization, and bundler module resolution mode.
| Version | Year | Key Additions | | --- | --- | --- | | 0.8 - 1.x | 2012-2015 | Types, interfaces, classes, modules, generics | | 2.x | 2016-2018 | strictNullChecks, mapped types, conditional types | | 3.x | 2018-2020 | unknown type, rest generics, project references | | 4.x | 2020-2023 | Variadic tuples, template literals, const assertions | | 5.0-5.4 | 2023-2024 | New decorators, const type params, ES module improvements | | 5.5-5.7 | 2025 | Inferred predicates, isolated declarations, strictBuiltinIteratorReturn | | 5.8+ | 2026 | Erasable syntax types, enhanced narrowing, new strictness options |
TypeScript 5.8 in 2026 introduced erasable type syntax - a significant step toward a future where type annotations can exist in JavaScript files that are executed directly without compilation, enabled by the TC39 Type Annotations proposal progressing through the JavaScript standards process.
Getting started with TypeScript in 2026 is considerably more streamlined than it was in earlier years. The ecosystem has standardized, tooling has improved, and most popular project scaffolding tools generate TypeScript configurations by default. This section covers everything you need to set up a professional TypeScript development environment.
Shell
# Install TypeScript globally
npm install -g typescript
# Check version (should be 5.x in 2026)
tsc --version
# Install in a project (preferred for reproducible builds)
npm install --save-dev typescript @types/node
# Initialize tsconfig.json
npx tsc --init
# Alternative: use Bun (much faster)
bun add -d typescript
bun tsc --init
Thetsconfig.jsonfile controls every aspect of TypeScript compilation. In 2026, the recommended base configuration for new projects is thestrictmode with additional modern options enabled.
tsconfig.json
{
"compilerOptions": {
/* Language and Environment */
"target": "ES2022",
"lib": ["ES2022", "DOM", "DOM.Iterable"],
"useDefineForClassFields": true,
/* Module resolution */
"module": "NodeNext",
"moduleResolution": "NodeNext",
"resolveJsonModule": true,
/* Output */
"outDir": "./dist",
"rootDir": "./src",
"declaration": true,
"declarationMap": true,
"sourceMap": true,
/* Type Checking - ALL strict options */
"strict": true,
"noUncheckedIndexedAccess": true,
"noImplicitOverride": true,
"exactOptionalPropertyTypes": true,
"noPropertyAccessFromIndexSignature": true,
/* JS Support */
"allowJs": false,
"skipLibCheck": true,
/* Interop */
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"isolatedModules": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist", "**/*.test.ts"]
}
Shell
# React + TypeScript (most common in 2026)
npm create vite@latest my-app -- --template react-ts
# Vanilla TypeScript
npm create vite@latest my-app -- --template vanilla-ts
# Node.js + TypeScript backend
npm create vite@latest my-api -- --template node-ts
# Next.js (auto-configures TypeScript)
npx create-next-app@latest my-next-app --typescript
TypeScript's type system is the engine that powers everything else. Understanding how types work - not just the syntax, but the underlying structural typing model and type inference - is the foundation of TypeScript mastery. This section covers all primitive types, structural typing, type inference, and the fundamental ways TypeScript reasons about values.
TypeScript
// The 8 primitive types in TypeScript
const name: string = "TypeScript";
const version: number = 5.8;
const major: bigint = 9007199254740993n;
const isTyped: boolean = true;
const nothing: null = null;
let pending: undefined;
const id: symbol = Symbol("unique");
const bytes: object = { raw: new Uint8Array(8) };
// Type inference - TypeScript infers types when obvious
const language = "TypeScript"; // inferred: string
const pi = 3.14159; // inferred: number
const pair = [1, "hello"]; // inferred: (number | string)[]
// Literal types: restrict to specific values
const direction: "north" | "south" | "east" | "west" = "north";
const httpCode: 200 | 201 | 400 | 401 | 404 | 500 = 200;
// const assertion: infer narrowest possible type
const config = {
host: "localhost",
port: 3000,
env: "development"
} as const;
// config.env: "development" (not string)
// config.port: 3000 (not number)
TypeScript
// Union types: one of these
type StringOrNumber = string | number;
type ID = string | number | `${string}-${number}`;
// Intersection types: combine all properties
type Timestamped = { createdAt: Date; updatedAt: Date };
type Named = { name: string };
type TimestampedEntity = Named & Timestamped;
// any: escape hatch (use sparingly)
let data: any = fetchFromLegacyAPI();
// unknown: safe alternative to any
let userInput: unknown = getInput();
// Must narrow before using:
if (typeof userInput === "string") {
console.log(userInput.toUpperCase()); // safe
}
// never: the empty type (unreachable code)
function throwError(msg: string): never {
throw new Error(msg);
}
// void: function returns nothing meaningful
function log(msg: string): void {
console.log(msg);
}
TypeScript usesstructural typing(also called "duck typing"): two types are compatible if they have the same structure, regardless of their names. This is different from nominal typing (used in Java or C#), where two types are only compatible if they explicitly declare a relationship.
TypeScript
interface Point { x: number; y: number }
interface Coordinate { x: number; y: number }
// These are structurally identical - fully compatible
const p: Point = { x: 1, y: 2 };
const c: Coordinate = p; // ✅ Works - same structure
// Extra properties are allowed when assigned to broader type
interface Named { name: string }
const user = { name: "Alice", age: 30 };
const named: Named = user; // ✅ user has at least { name: string }
// Excess property checking on direct object literals
const named2: Named = { name: "Bob", age: 25 };
// ❌ Error: 'age' does not exist in type 'Named'
Interfaces and type aliases are the two primary ways to define object shapes in TypeScript. While they are similar in many ways, they have important differences that affect when to use each. In 2026, the preferred convention is to useinterfacefor object shapes that may be extended or implemented, andtypefor everything else - union types, intersection types, mapped types, conditional types, and complex type transformations.
TypeScript
// Basic interface
interface User {
readonly id: number; // cannot be reassigned
name: string;
email: string;
age?: number; // optional property
role: "admin" | "user" | "guest";
createdAt: Date;
}
// Interface with methods
interface Repository<T> {
findById(id: string): Promise<T | null>;
findAll(): Promise<T[]>;
create(data: Omit<T, "id" | "createdAt">): Promise<T>;
update(id: string, data: Partial<T>): Promise<T>;
delete(id: string): Promise<void>;
}
// Interface extension (inheritance)
interface AdminUser extends User {
permissions: string[];
lastLogin: Date;
}
// Multiple extension
interface AuditableAdmin extends AdminUser, Timestamped {
auditLog: AuditEntry[];
}
// Declaration merging: interfaces with same name merge
interface Window {
myAnalytics: Analytics;
}
// Now window.myAnalytics is typed correctly
TypeScript
// Type aliases for primitives and unions
type ID = string | number;
type Nullable<T> = T | null;
type Maybe<T> = T | null | undefined;
// Discriminated unions - essential pattern for safe state
type ApiState<T> =
| { status: "idle" }
| { status: "loading" }
| { status: "success"; data: T }
| { status: "error"; error: Error; retryCount: number };
function renderState<T>(state: ApiState<T>) {
switch (state.status) {
case "idle": return "Idle";
case "loading": return "Loading...";
case "success": return state.data; // data available here
case "error": return state.error.message;
}
}
// Template literal types
type EventName<T extends string> = `on${Capitalize<T>}`;
type ClickEvent = EventName<"click">; // "onClick"
type ChangeEvent = EventName<"change">; // "onChange"
type CSSProperty = `--${string}`;
type Getter<T extends string> = `get${Capitalize<T>}`;
type Setter<T extends string> = `set${Capitalize<T>}`;
Generics are TypeScript's most powerful feature for writing reusable, type-safe code. A generic type is parameterized by one or more type variables, allowing a function, class, or interface to work with many different types while maintaining full type safety. Understanding generics deeply is what separates intermediate TypeScript developers from advanced ones.
TypeScript
// Generic identity function
function identity<T>(value: T): T {
return value;
}
identity("hello"); // T = string, returns string
identity(42); // T = number, returns number
// Generic with constraint
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const user = { name: "Alice", age: 30 };
getProperty(user, "name"); // type: string ✅
getProperty(user, "xyz"); // ❌ Error: "xyz" is not a key
// Practical generic: API wrapper
async function fetchData<T>(
url: string,
options?: RequestInit
): Promise<ApiState<T>> {
try {
const res = await fetch(url, options);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const data: T = await res.json();
return { status: "success", data };
} catch (e) {
return { status: "error", error: e as Error, retryCount: 0 };
}
}
// Usage - T inferred from expected type annotation
const result = await fetchData<User>("/api/user/1");
if (result.status === "success") {
console.log(result.data.name); // name: string ✅
}
TypeScript
// Generic Stack implementation
class Stack<T> {
private items: T[] = [];
push(item: T): void { this.items.push(item); }
pop(): T {
if (this.items.length === 0) throw new Error("Stack is empty");
return this.items.pop()!;
}
peek(): T | undefined {
return this.items[this.items.length - 1];
}
get size(): number { return this.items.length; }
get isEmpty(): boolean { return this.items.length === 0; }
[Symbol.iterator](): Iterator<T> {
return [...this.items].reverse()[Symbol.iterator]();
}
}
const numStack = new Stack<number>();
numStack.push(1); numStack.push(2); numStack.push(3);
console.log(numStack.pop()); // 3 - fully typed
// Const type parameter (TS 5.0) - infer literal type
function createTuple<const T extends readonly unknown[]>(...args: T): T {
return args;
}
const t = createTuple("hello", 42, true);
// type: readonly ["hello", 42, true] - literal types preserved
TypeScript's type system is Turing-complete - meaning you can compute types at the type level with the same expressiveness as runtime computations. This section covers the most powerful and commonly used advanced type patterns in professional TypeScript development in 2026.
TypeScript
// Conditional types: T extends U ? X : Y
type IsString<T> = T extends string ? "yes" : "no";
type A = IsString<string>; // "yes"
type B = IsString<number>; // "no"
// infer keyword: extract type from structure
type ReturnType<T> =
T extends (...args: any[]) => infer R ? R : never;
type PromiseValue<T> =
T extends Promise<infer V> ? V : T;
type DeepPromiseValue<T> =
T extends Promise<infer V>
? DeepPromiseValue<V>
: T; // recursive unwrapping
type Flatten<T> = T extends Array<infer Item> ? Item : T;
type Num = Flatten<number[]>; // number
type Str = Flatten<string>; // string (not an array)
TypeScript
// Mapped types: transform every property of a type
type Readonly<T> = {
readonly [K in keyof T]: T[K];
};
type Partial<T> = {
[K in keyof T]?: T[K];
};
// Remove readonly modifier
type Mutable<T> = {
-readonly [K in keyof T]: T[K];
};
// Remove optional modifier
type Required<T> = {
[K in keyof T]-?: T[K];
};
// Rename keys with remapping (TS 4.1)
type Getters<T> = {
[K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};
type UserGetters = Getters<{ name: string; age: number }>;
// { getName: () => string; getAge: () => number }
// Filter keys by value type
type FilterByValue<T, V> = {
[K in keyof T as T[K] extends V ? K : never]: T[K];
};
type OnlyStrings = FilterByValue<
{ name: string; age: number; email: string },
string
>;
// { name: string; email: string }
💡 Pro Tip
Advanced mapped types and conditional types are the foundation of most popular TypeScript utility libraries. Understanding them deeply allows you to build your own type utilities tailored to your application's needs, rather than depending on third-party type packages for every edge case.
TypeScript provides the most complete and well-typed class system available in the JavaScript ecosystem. Building on JavaScript's native class syntax, TypeScript adds access modifiers, abstract classes, parameter properties, override checking, and full generics support for classes.
TypeScript
abstract class BaseEntity {
readonly id: string;
createdAt: Date;
updatedAt: Date;
constructor(id?: string) {
this.id = id ?? crypto.randomUUID();
this.createdAt = new Date();
this.updatedAt = new Date();
}
abstract validate(): boolean;
touch(): void {
this.updatedAt = new Date();
}
toJSON(): Record<string, unknown> {
return {
id: this.id,
createdAt: this.createdAt.toISOString(),
updatedAt: this.updatedAt.toISOString(),
};
}
}
class User extends BaseEntity {
// Parameter property shorthand
constructor(
public name: string,
public email: string,
private #passwordHash: string,
protected role: "admin" | "user" = "user"
) {
super();
}
override validate(): boolean {
return this.email.includes("@") && this.name.length > 0;
}
static create(
name: string,
email: string,
password: string
): User {
const hash = hashPassword(password);
return new User(name, email, hash);
}
get displayName(): string {
return `${this.name} <${this.email}>`;
}
}
TypeScript
// Mixin: composable class extensions
type Constructor<T = {}> = new (...args: any[]) => T;
function Serializable<TBase extends Constructor>(Base: TBase) {
return class extends Base {
serialize(): string {
return JSON.stringify(this);
}
static deserialize<T>(json: string): T {
return JSON.parse(json) as T;
}
};
}
function Timestamped<TBase extends Constructor>(Base: TBase) {
return class extends Base {
createdAt = new Date();
updatedAt = new Date();
touch() { this.updatedAt = new Date(); }
};
}
// Compose multiple mixins
class Product extends Serializable(Timestamped(BaseEntity)) {
constructor(public name: string, public price: number) { super(); }
validate() { return this.price > 0; }
}
const p = new Product("Laptop", 1299);
p.serialize(); // from Serializable mixin
p.touch(); // from Timestamped mixin
TypeScript's function typing system is comprehensive, supporting parameter types, return types, optional and default parameters, rest parameters, overloads, and higher-order function types. This section covers all aspects of function typing with a focus on patterns that appear in real-world TypeScript codebases.
TypeScript
// Function overloads: multiple call signatures
function format(value: string): string;
function format(value: number, decimals?: number): string;
function format(value: Date, locale?: string): string;
function format(value: string | number | Date, optionA?: number | string): string {
if (typeof value === "string") return value.trim();
if (typeof value === "number") return value.toFixed(optionA as number ?? 2);
return value.toLocaleDateString(optionA as string);
}
format(" hello "); // "hello"
format(3.14159, 2); // "3.14"
format(new Date(), "en-IN"); // "15/06/2026"
// Higher-order function types
type Predicate<T> = (value: T) => boolean;
type Transformer<T, U> = (value: T) => U;
type Reducer<T, R> = (acc: R, cur: T, idx: number) => R;
function pipe<T>(value: T, ...fns: Transformer<T, T>[]): T {
return fns.reduce((v, fn) => fn(v), value);
}
const result = pipe(
" Hello World ",
(s) => s.trim(),
(s) => s.toLowerCase(),
(s) => s.replace(/\s+/g, "-")
);
// "hello-world"
TypeScript's module system is built on the ES module standard (import/export), the same system used in modern JavaScript. Understanding how TypeScript resolves modules, manages type declarations, and integrates with different bundler and runtime environments is essential for building maintainable TypeScript projects.
TypeScript
// Named exports
export { UserService, UserRepository };
export { UserService as default };
// Type-only imports/exports (TypeScript 3.8+)
import type { User, Role } from "./types";
export type { ApiResponse, PaginatedResult };
// Inline type import (TS 4.5+)
import { type Config, loadConfig } from "./config";
// Re-export patterns
export * from "./user";
export * as utils from "./utils";
export { UserService } from "./services/user";
// Dynamic import with type safety
const loadHeavyModule = async () => {
const { HeavyProcessor } = await import("./heavy");
return new HeavyProcessor();
};
// Augmenting third-party modules
declare module "express" {
interface Request {
user?: User;
requestId: string;
}
}
Declaration files are pure type information files with a.d.tsextension. They describe the types of JavaScript modules, library APIs, or global variables without containing any actual runtime code. The@typesscoped packages on npm contain declaration files for thousands of popular JavaScript libraries, allowing TypeScript to understand and type-check code that uses those libraries.
TypeScript (.d.ts)
// my-library.d.ts
declare module "my-legacy-lib" {
export interface Options {
timeout?: number;
retries?: number;
headers?: Record<string, string>;
}
export function request(url: string, opts?: Options): Promise<string>;
export const VERSION: string;
}
// Ambient declarations for global APIs
declare global {
interface Window {
gtag: Gtag;
dataLayer: unknown[];
}
const __DEV__: boolean;
const __VERSION__: string;
}
TypeScript 5.0 introduced the new Stage 3 decorator standard, a major redesign of the previous experimental decorator system. The new decorators are fully standardized, aligned with the TC39 proposal, and compatible with the direction JavaScript itself is taking. Decorators in TypeScript 5.x are a first-class metaprogramming feature that allows you to add behavior to classes, methods, accessors, fields, and getters/setters at the point of declaration.
TypeScript 5.x Decorators
// Method decorator: log execution time
function trace(target: unknown, ctx: ClassMethodDecoratorContext) {
return function(this: unknown, ...args: unknown[]) {
const start = performance.now();
const result = (target as Function).call(this, ...args);
const end = performance.now();
console.log(`[${String(ctx.name)}] ${(end-start).toFixed(2)}ms`);
return result;
};
}
// Class decorator: add metadata
function injectable(target: Function, ctx: ClassDecoratorContext) {
Reflect.defineMetadata("injectable", true, target);
}
// Field decorator: validate values
function minLength(min: number) {
return function(target: undefined, ctx: ClassFieldDecoratorContext) {
return function(this: unknown, value: string): string {
if (value.length < min) {
throw new Error(`${String(ctx.name)} must be ≥ ${min} chars`);
}
return value;
};
};
}
@injectable
class UserService {
@minLength(2)
declare name: string;
@trace
async findUser(id: string): Promise<User> {
return await fetchUser(id);
}
}
React and TypeScript are one of the most powerful and widely-used combinations in modern frontend development. TypeScript dramatically improves the React development experience by providing type checking for props, state, context, hooks, and event handlers. In 2026, starting a React project without TypeScript is unusual for any serious team.
TypeScript + React
import { FC, useState, useCallback, useRef, ReactNode } from "react";
// Component props interface
interface ButtonProps {
children: ReactNode;
variant?: "primary" | "secondary" | "danger";
size?: "sm" | "md" | "lg";
disabled?: boolean;
loading?: boolean;
onClick?: (e: React.MouseEvent<HTMLButtonElement>) => void;
className?: string;
}
const Button: FC<ButtonProps> = ({
children, variant = "primary",
size = "md", disabled = false,
loading = false, onClick, className
}) => (
<button
className={[`btn btn--${variant} btn--${size}`, className].join(" ")}
disabled={disabled || loading}
onClick={onClick}
aria-busy={loading}
>
{loading ? <Spinner /> : children}
</button>
);
// Generic component
interface SelectProps<T> {
options: T[];
value: T | null;
onChange: (value: T) => void;
getLabel: (option: T) => string;
getKey: (option: T) => string;
}
function Select<T>({ options, value, onChange, getLabel, getKey }: SelectProps<T>) {
return (
<select value={value ? getKey(value) : ""}
onChange={e => {
const opt = options.find(o => getKey(o) === e.target.value);
if (opt) onChange(opt);
}}>
{options.map(opt => (
<option key={getKey(opt)} value={getKey(opt)}>{getLabel(opt)}</option>
))}
</select>
);
}
TypeScript + React Hooks
// Typed custom hook
function useFetch<T>(url: string): ApiState<T> {
const [state, setState] = useState<ApiState<T>>({ status: "idle" });
useEffect(() => {
let cancelled = false;
setState({ status: "loading" });
fetchData<T>(url).then(result => {
if (!cancelled) setState(result);
});
return () => { cancelled = true; };
}, [url]);
return state;
}
// useLocalStorage with generics
function useLocalStorage<T>(
key: string,
initialValue: T
): [T, (value: T | ((prev: T) => T)) => void] {
const [stored, setStored] = useState<T>(() => {
try {
const item = localStorage.getItem(key);
return item ? JSON.parse(item) : initialValue;
} catch { return initialValue; }
});
const setValue = useCallback(
(value: T | ((prev: T) => T)) => {
const next = typeof value === "function"
? (value as (p: T) => T)(stored) : value;
setStored(next);
localStorage.setItem(key, JSON.stringify(next));
}, [key, stored]
);
return [stored, setValue];
}
TypeScript has become the standard language for serious Node.js development. The combination of TypeScript's type safety with Node.js's performance and extensive package ecosystem is extremely powerful. In 2026, setting up a TypeScript Node.js project is simpler than ever, with native TypeScript support coming in some runtimes.
TypeScript + Express (Node.js)
import express, { Request, Response, NextFunction } from "express";
// Augment Express Request to add user
declare global {
namespace Express {
interface Request {
user?: AuthUser;
requestId: string;
}
}
}
// Typed route handler wrapper
type AsyncHandler<P = {}, ResBody = unknown, ReqBody = unknown> = (
req: Request<P, ResBody, ReqBody>,
res: Response<ResBody>,
next: NextFunction
) => Promise<void>;
function asyncHandler<P, R, B>(fn: AsyncHandler<P, R, B>) {
return (req: Request<P, R, B>, res: Response<R>, next: NextFunction) => {
fn(req, res, next).catch(next);
};
}
// Typed request body
interface CreateUserBody {
name: string;
email: string;
password: string;
}
const router = express.Router();
router.post(
"/users",
asyncHandler<{}, User, CreateUserBody>(async (req, res) => {
const { name, email, password } = req.body; // fully typed
const user = await UserService.create({ name, email, password });
res.status(201).json(user);
})
);
TypeScript 5.x (spanning from 5.0 to the 5.8+ releases in 2026) introduced a wide range of significant features that improve developer productivity, type safety, and compatibility with emerging JavaScript standards. This section provides a comprehensive overview of the most important TypeScript 5.x additions.
TypeScript 5.5
// Before TS 5.5 - had to write explicit type predicate
function isString(val: unknown): val is string {
return typeof val === "string";
}
// TS 5.5+ - type predicate inferred automatically!
function isString(val: unknown) {
return typeof val === "string";
// return type inferred as: val is string
}
// This means .filter() now correctly narrows type!
const mixed = [1, "hello", 2, "world", null];
const strings = mixed.filter(isString);
// type: string[] ← correctly narrows (was (string|number|null)[] before)
const nonNull = mixed.filter(v => v !== null);
// type: (string | number)[] ← also narrows correctly!
TheisolatedDeclarationscompiler option (TS 5.5) enforces that every exported declaration has an explicit type annotation - no relying on type inference for public API exports. This enables much faster type declaration generation, allowing tools like esbuild and oxc to generate.d.tsfiles without running the full TypeScript type checker. For large monorepos, this can reduce declaration generation from minutes to seconds.
TypeScript 5.6
// TS 5.6 - strictBuiltinIteratorReturn
// Builtin iterators like Array.prototype[Symbol.iterator] now return
// BuiltinIterator instead of Iterator, with TReturn = undefined
const iter = [1, 2, 3][Symbol.iterator]();
const result = iter.next();
// result: IteratorResult<number, undefined>
// result.value is number | undefined (was any before)
// Enables safe exhaustion checking
while (true) {
const { value, done } = iter.next();
if (done) break;
console.log(value); // type: number (not number | undefined)
}
TypeScript 5.8+ introduced a new option"erasableSyntaxOnly"that restricts TypeScript files to only use type syntax that can be erased (removed) to produce valid JavaScript, without requiring any transformation. This aligns TypeScript with the TC39 Type Annotations proposal and enables runtimes like Node.js (with its--experimental-strip-typesflag, available since Node.js 22) to strip TypeScript annotations natively.
| Feature | TS Version | Description | | --- | --- | --- | | New Decorators | 5.0 | Stage 3 decorator standard implementation | | const type params | 5.0 | Infer literal types in generic type parameters | | Variadic tuple labels | 5.0 | Named elements in tuple types | | Inferred predicates | 5.5 | Auto-infer type guard predicates | | isolatedDeclarations | 5.5 | Explicit exports for fast .d.ts generation | | Strict iterator return | 5.6 | Safer iterator exhaustion types | | Erasable syntax | 5.8 | Alignment with TC39 Type Annotations proposal |
TypeScript includes a comprehensive set of built-in utility types that perform common type transformations. These are available globally without any imports. In 2026, fluency with all the built-in utility types is a baseline expectation for professional TypeScript developers.
TypeScript - All Utility Types
interface User {
id: number; name: string;
email: string; age?: number;
role: "admin" | "user";
}
// Partial<T> - all properties optional
type UpdateUser = Partial<User>;
// Required<T> - all properties required
type FullUser = Required<User>;
// Readonly<T> - all properties readonly
type ImmutableUser = Readonly<User>;
// Pick<T, K> - select specific keys
type UserSummary = Pick<User, "id" | "name">;
// Omit<T, K> - remove specific keys
type CreateUserInput = Omit<User, "id">;
// Exclude<T, U> - remove from union
type NonAdmin = Exclude<User["role"], "admin">; // "user"
// Extract<T, U> - keep matching union members
type AdminRole = Extract<User["role"], "admin">; // "admin"
// NonNullable<T> - remove null and undefined
type DefinedAge = NonNullable<User["age"]>; // number
// ReturnType<T> - return type of function
const getUser = () => ({ id: 1, name: "Alice" });
type GetUserReturn = ReturnType<typeof getUser>;
// Parameters<T> - parameter types as tuple
type GetUserParams = Parameters<typeof getUser>;
// Awaited<T> - unwrap Promise type
type UserFromAPI = Awaited<ReturnType<typeof fetchUser>>;
// Record<K, V> - key-value map type
type UserMap = Record<string, User>;
type StatusMap = Record<"active" | "inactive", User[]>;
// InstanceType<T> - instance type of constructor
class UserModel { id = 0; name = ""; }
type UserInstance = InstanceType<typeof UserModel>;
Type narrowing is one of TypeScript's most powerful features. TypeScript's control flow analysis tracks the type of a variable through different branches of your code, automatically narrowing the type based on type checks, assignments, and logical conditions. Understanding all the ways TypeScript narrows types allows you to write code that is both safe and ergonomic.
TypeScript
// 1. typeof guards
function processInput(input: string | number | boolean) {
if (typeof input === "string") return input.toUpperCase();
if (typeof input === "number") return input * 2;
return !input; // narrowed to boolean
}
// 2. instanceof guards
function handleError(err: unknown) {
if (err instanceof TypeError) console.log("Type error:", err.message);
else if (err instanceof RangeError) console.log("Range:", err.message);
else if (err instanceof Error) console.log("Error:", err.message);
else console.log("Unknown error");
}
// 3. User-defined type guards
function isUser(val: unknown): val is User {
return (
typeof val === "object" && val !== null &&
"id" in val && "name" in val && "email" in val
);
}
// 4. Assertion functions (TS 3.7+)
function assertDefined<T>(
val: T | undefined | null,
msg: string
): asserts val is T {
if (val == null) throw new Error(msg);
}
const user: User | undefined = getUser();
assertDefined(user, "User not found");
user.name; // type: User (not User | undefined)
// 5. Discriminated union exhaustiveness
function assertNever(x: never): never {
throw new Error(`Unexpected value: ${x}`);
}
type Shape =
| { kind: "circle"; radius: number }
| { kind: "rect"; width: number; height: number }
| { kind: "triangle"; base: number; h: number };
function area(s: Shape): number {
switch (s.kind) {
case "circle": return Math.PI * s.radius ** 2;
case "rect": return s.width * s.height;
case "triangle": return 0.5 * s.base * s.h;
default: return assertNever(s);
// If a new Shape variant is added, TS errors here → exhaustive check
}
}
TypeScript enables significantly safer error handling than plain JavaScript, but it requires deliberate design choices. The default JavaScripttry/catchpattern has a significant TypeScript limitation: the caught variable has typeunknown(withuseUnknownInCatchVariables: true, which is part ofstrict). This is actually correct - you cannot know what type an error is at compile time - but it requires you to handle it properly.
TypeScript
// Result pattern: type-safe error handling without exceptions
type Ok<T> = { ok: true; value: T };
type Err<E> = { ok: false; error: E };
type Result<T, E = Error> = Ok<T> | Err<E>;
const ok = <T>(value: T): Ok<T> => ({ ok: true, value });
const err = <E>(error: E): Err<E> => ({ ok: false, error });
async function parseUserFromRequest(
body: unknown
): Promise<Result<User, ValidationError>> {
if (!isUser(body)) {
return err(new ValidationError("Invalid user data"));
}
return ok(body);
}
// Caller handles both cases explicitly
const result = await parseUserFromRequest(req.body);
if (!result.ok) {
return res.status(400).json({ error: result.error.message });
}
// result.value is User here - no null check needed
await UserService.create(result.value);
TypeScript testing in 2026 is primarily done with Vitest (the preferred choice for Vite-based projects and increasingly everywhere), with Jest remaining common in established codebases. TypeScript introduces important considerations for testing: type-level tests (testing that your types are correct), mock typing (ensuring mocks match the interface being mocked), and test utility types.
TypeScript + Vitest
import { describe, it, expect, vi, beforeEach } from "vitest";
import { UserService } from "./user.service";
import type { UserRepository } from "./user.repository";
// Type-safe mock with vi.fn()
const createMockRepo = (): UserRepository => ({
findById: vi.fn(),
findAll: vi.fn(),
create: vi.fn(),
update: vi.fn(),
delete: vi.fn(),
});
describe("UserService", () => {
let repo: UserRepository;
let service: UserService;
beforeEach(() => {
repo = createMockRepo();
service = new UserService(repo);
});
it("findUser returns user by id", async () => {
const mockUser: User = {
id: 1, name: "Alice", email: "alice@test.com",
role: "user", createdAt: new Date()
};
vi.mocked(repo.findById).mockResolvedValue(mockUser);
const user = await service.findUser("1");
expect(user).toEqual(mockUser);
expect(repo.findById).toHaveBeenCalledWith("1");
});
it("throws when user not found", async () => {
vi.mocked(repo.findById).mockResolvedValue(null);
await expect(service.findUser("999"))
.rejects.toThrow("User not found");
});
});
TypeScript
import { expectTypeOf } from "vitest";
it("fetchData returns correct type", () => {
// These assertions run at compile time AND runtime
expectTypeOf(fetchData<User>)
.returns
.toEqualTypeOf<Promise<ApiState<User>>>();
expectTypeOf(identity<string>)
.toBeCallableWith("hello")
.returns.toBeString();
});
TypeScript compilation performance matters enormously in large projects. Slow type checking slows down development feedback loops, CI pipelines, and developer productivity. In 2026, TypeScript provides several strategies for improving compilation performance, and the ecosystem has developed tools to help with incremental builds, type checking in parallel, and separating transpilation from type checking.
isolatedModules: true- Restricts code to patterns that can be transpiled file-by-file without full type information. Required for esbuild/Babel/SWC transpilation."incremental": truesaves type information to.tsbuildinfofiles, allowing subsequent compilations to only recheck changed files."references"in tsconfig. Each project builds independently and caches results.tsc --noEmitonly for type checking in CI.skipLibCheck: true- Skip type checking of.d.tsfiles in node_modules. Usually safe and provides significant speedup for projects with many dependencies.package.json scripts
{
"scripts": {
// Fast dev: esbuild transpiles, no type checking
"dev": "vite --host",
// Type check only (no emit) - run in parallel with dev
"typecheck": "tsc --noEmit",
"typecheck:watch": "tsc --noEmit --watch",
// Build: type check + emit
"build": "tsc && vite build",
// CI: parallel type check + tests
"ci:check": "tsc --noEmit & vitest run & wait",
// Library build with declarations
"build:lib": "tsc -p tsconfig.build.json"
}
}
Design patterns take on new dimensions in TypeScript. The type system allows many patterns that would be error-prone in JavaScript to be expressed with complete safety. This section covers the most important design patterns with their TypeScript implementations.
TypeScript
// Builder pattern with type tracking
// T tracks which required fields have been set
type BuilderState<T, Set extends keyof T = never> = {
[K in keyof T]?: T[K];
} & {
[K in Set]: T[K]; // required fields are definitely set
};
class QueryBuilder<TSet extends string = never> {
private _table?: string;
private _conditions: string[] = [];
private _limit?: number;
private _orderBy?: string;
from(table: string): QueryBuilder<TSet | "table"> {
this._table = table;
return this as any;
}
where(...conditions: string[]): this {
this._conditions.push(...conditions);
return this;
}
limit(n: number): this {
this._limit = n;
return this;
}
// build() only available when "table" has been set
build(this: QueryBuilder<TSet | "table">): string {
let q = `SELECT * FROM ${this._table}`;
if (this._conditions.length)
q += ` WHERE ${this._conditions.join(" AND ")}`;
if (this._limit) q += ` LIMIT ${this._limit}`;
return q;
}
}
const query = new QueryBuilder()
.from("users")
.where("active = true", "age > 18")
.limit(10)
.build(); // ✅ only works after .from()
TypeScript's trajectory in 2027 and beyond is shaped by two forces: the evolution of the JavaScript language itself (TC39 proposals) and TypeScript's own roadmap for type system improvements and developer experience enhancements. Several significant developments are expected to arrive or mature through 2027.
The most consequential development for TypeScript's future is the TC39 Type Annotations proposal (Stage 2 as of 2026). If accepted and implemented in JavaScript engines, this proposal would allow type annotations in JavaScript source files that are treated as comments by the engine - no compilation required. TypeScript 5.8'serasableSyntaxOnlymode is directly aligned with this future.
This means in a 2027 world, you might write TypeScript syntax in.tsfiles and run them directly in Node.js with just--experimental-strip-types, or potentially without any flags in future Node versions. The compilation step would become optional for simple cases, while still required for features that transform syntax (enums, namespaces, older decorators).
The TypeScript team's GitHub issues and roadmap reveal several upcoming improvements: higher-kinded types (typing functions that take generic type constructors), improved tuple type inference, better type-level string manipulation, stricter handling ofthisparameters, and continued improvements to control flow analysis for complex narrowing scenarios.
TypeScript (Future Proposals)
// Type Annotations proposal (potential 2027 syntax)
// This runs directly in Node.js - no tsc needed!
function greet(name: string): string {
return `Hello, ${name}!`;
}
// Types are syntax-only - engines ignore them
// No transformation, no sourcemaps needed
// Nominal/branded types (more ergonomic in future)
type UserId = string & { readonly brand: unique symbol };
type OrderId = string & { readonly brand: unique symbol };
// Can't accidentally pass OrderId where UserId expected
declare function createOrder(userId: UserId): Promise<OrderId>;
const orderId = "ord-123" as OrderId;
// createOrder(orderId); // ❌ Type Error - orderId is not UserId
In 2026-2027, TypeScript's rich type information is becoming increasingly valuable for AI coding assistants. Tools like GitHub Copilot, Cursor, and others use TypeScript type information to generate more accurate, correctly-typed code suggestions. The type annotations in TypeScript code serve as precise documentation that AI tools can use to understand the intended API contract. Well-typed TypeScript codebases are significantly easier for AI assistants to contribute to accurately than untyped JavaScript.
🔮 2027 Predictions
TypeScript will likely cross 90% adoption among professional JavaScript developers. The TC39 Type Annotations proposal may reach Stage 4. Native TypeScript execution without compilation will become common for Node.js development. TypeScript's type system capabilities will continue expanding with features addressing higher-kinded types and dependent types use cases.
TypeScript expertise has become one of the most marketable technical skills in software engineering. Companies of all sizes - from early-stage startups to large enterprises - now require or strongly prefer TypeScript proficiency for frontend and full-stack engineering roles. This section provides a comprehensive guide to building TypeScript skills and advancing your career.
strictmode from day one.| Resource | Type | Best For | | --- | --- | --- | | TypeScript Handbook (typescriptlang.org) | Official docs | Authoritative reference for all features | | TypeScript Playground | Interactive tool | Experimenting with types, sharing examples | | Type Challenges (github) | Practice | Advanced type-level programming exercises | | Total TypeScript (mattpocock.com) | Course/newsletter | Professional TS patterns and workshops | | TS Deep Dive (basarat.gitbook.io) | Free book | Comprehensive free reference | | TypeScript Weekly | Newsletter | Staying current with TS ecosystem | | definitely-typed (DefinitelyTyped) | Repository | Community type declarations for JS packages | | tsconfig bases (tsconfig.bases) | Package | Recommended tsconfig presets by environment |
TypeScript proficiency commands a significant premium in the job market. Senior TypeScript engineers are among the highest-paid developers globally. The demand is particularly strong in three segments: frontend engineering at technology companies (React + TypeScript is the dominant stack), backend Node.js/Bun development at API-first companies, and full-stack development roles that span both frontend and backend TypeScript systems.
The most in-demand TypeScript specializations in 2026 are: TypeScript with React Server Components (Next.js 15+), TypeScript with edge computing platforms (Cloudflare Workers, Vercel Edge), TypeScript for AI application development (building LLM-powered apps), and TypeScript for design system and component library development.
The strongest TypeScript portfolios demonstrate not just that you can use TypeScript, but that you understand its type system deeply. Projects that showcase TypeScript expertise include: an open-source library with a well-designed type-safe API and complete type declarations; a complex generic data structure (like a typed event emitter or type-safe state machine); a full-stack application using TypeScript end-to-end from React frontend to Node.js backend with shared types; and contributions to DefinitelyTyped or TypeScript itself.
TypeScript in 2026 is not just a tool - it is the professional standard for JavaScript development. The type system, which started as a simple annotation layer, has grown into a sophisticated system capable of expressing complex type relationships, enforcing invariants at compile time, and providing developer tooling that was previously impossible for dynamically-typed languages.
The investment in learning TypeScript deeply pays enormous dividends: fewer bugs, better tooling, safer refactoring, more maintainable codebases, and higher productivity for teams of all sizes. The developers who truly master TypeScript's type system - not just its syntax - are among the most valuable engineers in the industry.
As TypeScript continues to evolve into 2027 and aligns more closely with JavaScript's own trajectory through the TC39 Type Annotations proposal, its role as the definitive way to write professional JavaScript will only strengthen. Understanding TypeScript is not just learning a tool - it is understanding the future of JavaScript development itself.
Keep practicing!
any
JavaScript Complete Guide
45 min - Beginner
Python for Beginners 15 Day Plan
40 min - Beginner
Java + DSA Interview Prep 2026-2027
35 min - Intermediate
Top 50 Java Interview Questions
25 min - Intermediate
Machine Learning Complete Guide
40 min - Intermediate
Artificial Intelligence Complete Guide
35 min - Intermediate