Typing Functions
Every function parameter should have a type. TypeScript infers the return type, but you can be explicit:
// Parameter types and inferred return type
function add(a: number, b: number) {
return a + b; // return type inferred as number
}
// Explicit return type
function greet(name: string): string {
return `Hello, ${name}!`;
}
// Arrow functions work the same way
const multiply = (a: number, b: number): number => a * b;Optional & Default Parameters
// Optional parameter (must come after required ones)
function createUser(name: string, age?: number) {
return { name, age: age ?? 0 };
}
createUser("Alice"); // OK
createUser("Bob", 25); // OK
// Default values (type is inferred from the default)
function paginate(items: string[], page = 1, pageSize = 10) {
const start = (page - 1) * pageSize;
return items.slice(start, start + pageSize);
}Rest Parameters
function sum(...numbers: number[]): number {
return numbers.reduce((total, n) => total + n, 0);
}
sum(1, 2, 3); // 6
sum(10, 20, 30, 40); // 100
Function Type Signatures
You can describe a function's shape as a type:
// Type alias for a function
type MathOp = (a: number, b: number) => number;
const add: MathOp = (a, b) => a + b;
const subtract: MathOp = (a, b) => a - b;
// As a callback parameter
function applyOp(a: number, b: number, op: MathOp): number {
return op(a, b);
}
applyOp(10, 5, add); // 15
applyOp(10, 5, subtract); // 5
Overloaded Functions
Function overloads let a single function handle different parameter types:
// Overload signatures
function format(value: string): string;
function format(value: number): string;
function format(value: Date): string;
// Implementation signature (must handle all overloads)
function format(value: string | number | Date): string {
if (typeof value === "string") return value.trim();
if (typeof value === "number") return value.toFixed(2);
return value.toISOString();
}
format(" hello "); // "hello"
format(3.14159); // "3.14"
format(new Date()); // "2026-03-12T00:00:00.000Z"
Introduction to Generics
Generics let you write functions that work with any type while still preserving type information:
// Without generics — loses type information
function firstElement(arr: any[]): any {
return arr[0];
}
const x = firstElement([1, 2, 3]); // type: any (not useful)
// With generics — preserves the type
function firstElement<T>(arr: T[]): T | undefined {
return arr[0];
}
const a = firstElement([1, 2, 3]); // type: number
const b = firstElement(["a", "b", "c"]); // type: string
The <T> is a type parameter — a placeholder that gets filled in when the function is called.
Generic Constraints
Sometimes you need to restrict what types a generic accepts:
// T must have a 'length' property
function longest<T extends { length: number }>(a: T, b: T): T {
return a.length >= b.length ? a : b;
}
longest("hello", "hi"); // "hello"
longest([1, 2, 3], [4, 5]); // [1, 2, 3]
// longest(10, 20); // Error: number doesn't have 'length'
Multiple Type Parameters
function mapObject<K extends string, V, R>(
obj: Record<K, V>,
fn: (value: V) => R
): Record<K, R> {
const result = {} as Record<K, R>;
for (const key in obj) {
result[key] = fn(obj[key]);
}
return result;
}
const prices = { apple: 1.5, banana: 0.75, cherry: 3.0 };
const formatted = mapObject(prices, (p) => `$${p.toFixed(2)}`);
// { apple: "$1.50", banana: "$0.75", cherry: "$3.00" }
Built-in Utility Types
TypeScript includes utility types that transform existing types:
interface User {
id: string;
name: string;
email: string;
age: number;
}
// Partial — all properties become optional
type UpdateUserDto = Partial<User>;
// { id?: string; name?: string; email?: string; age?: number }
// Required — all properties become required
type RequiredUser = Required<Partial<User>>;
// Pick — select specific properties
type UserPreview = Pick<User, "id" | "name">;
// { id: string; name: string }
// Omit — remove specific properties
type UserWithoutId = Omit<User, "id">;
// { name: string; email: string; age: number }
// Readonly — all properties become readonly
type FrozenUser = Readonly<User>;
Practical Example: A Generic API Client
interface ApiResponse<T> {
data: T;
status: number;
message: string;
}
async function fetchApi<T>(url: string): Promise<ApiResponse<T>> {
const res = await fetch(url);
if (!res.ok) {
throw new Error(`HTTP ${res.status}`);
}
return res.json();
}
// Usage — the type flows through automatically
interface Product {
id: string;
name: string;
price: number;
}
const response = await fetchApi<Product[]>("/api/products");
// response.data is Product[] — fully typed
response.data.forEach((p) => console.log(p.name, p.price));