function add(a: number, b: number): number {
return a + b;
}Return annotations can improve clarity even when TypeScript can infer them.
Function typing, overloads, control-flow narrowing, and async typing.
Typing function signatures, callbacks, overloads, and rest parameters.
function add(a: number, b: number): number {
return a + b;
}Return annotations can improve clarity even when TypeScript can infer them.
Mark a parameter as optional with `?`.
function greet(name: string, prefix?: string) {
return `${prefix ?? "Hello"}, ${name}`;
}Optional parameters become `T | undefined` in the body.
Provide a default value and keep a concrete type.
function connect(host = "localhost", port = 5432) {
return `${host}:${port}`;
}Default parameters often remove the need for manual undefined handling.
Accept a variable number of arguments.
function joinWith(separator: string, ...parts: string[]) {
return parts.join(separator);
}Rest parameters are typed as arrays.
Describe callback signatures explicitly.
type Predicate<T> = (value: T) => boolean;Function type aliases keep callback signatures reusable and readable.
Expose multiple call signatures with one implementation.
function format(value: number): string;
function format(value: Date): string;
function format(value: number | Date): string {
return value instanceof Date ? value.toISOString() : value.toFixed(2);
}Overloads help when parameter/return relationships are difficult to express with unions alone.
Use runtime checks so TypeScript can refine types safely.
Refine primitive unions using runtime checks.
function printId(id: string | number) {
if (typeof id === "string") {
console.log(id.toUpperCase());
} else {
console.log(id.toFixed(0));
}
}TypeScript uses control flow analysis to narrow `id` in each branch.
Refine class-based unions using constructor checks.
function formatValue(value: Date | Error) {
if (value instanceof Date) {
return value.toISOString();
}
return value.message;
}`instanceof` works well when runtime classes are available.
Refine unions by checking property existence.
type Dog = { bark(): void };
type Cat = { meow(): void };
function speak(animal: Dog | Cat) {
if ("bark" in animal) animal.bark();
else animal.meow();
}Property checks are especially useful for discriminating object-shape unions.
Catch unhandled union members.
type State = "idle" | "loading" | "success";
function render(state: State) {
switch (state) {
case "idle":
return "Idle";
case "loading":
return "Loading";
case "success":
return "Done";
default: {
const _exhaustive: never = state;
return _exhaustive;
}
}
}The `never` assignment fails if a new union member is added but not handled.
Teach TypeScript a reusable narrowing rule.
type User = { id: number; name: string };
const isUser = (value: unknown): value is User => {
return typeof value === "object" && value !== null && "id" in value && "name" in value;
};Type predicates are useful for validation boundaries like JSON input or external APIs.
Type async functions, promises, and result wrappers.
Return a typed promise from an async function.
async function fetchName(): Promise<string> {
return "Ada";
}Async functions always return a `Promise`, even when returning a plain value.
function wait(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}Useful for utilities, service functions, and browser or Node wrappers.
Represent success and failure without throwing.
type Ok<T> = { ok: true; value: T };
type Err = { ok: false; error: string };
type Result<T> = Ok<T> | Err;Result unions are a clean pattern for app code that wants explicit error handling.