JavaScript to TypeScript Functions
What changes when you add TypeScript types to JavaScript functions -- parameter annotations, return types, optional arguments, and overloads.
Parameter and Return Types
TypeScript requires type annotations on function parameters. This is the single biggest change from JavaScript: function greet(name) becomes function greet(name: string). Forgetting the annotation triggers a noImplicitAny error in strict mode. Return types can be annotated too (function greet(name: string): string), but they are optional -- TypeScript infers the return type from the function body. Whether to annotate return types is a team convention. Some codebases annotate everything for documentation; others rely on inference and only annotate when the inferred type is wider than intended. Arrow functions follow the same rules: (x: number) => x * 2 annotates the parameter, and the return type is inferred. When assigning an arrow function to a typed variable (const fn: (x: number) => number = (x) => x * 2), the parameter types flow from the variable type and the arrow function body does not need its own annotations. This "contextual typing" is one of TypeScript's ergonomic wins -- you define the shape once and the implementation inherits it.
Function declaration
function add(a, b) {
return a + b;
}function add(a: number, b: number): number {
return a + b;
}Arrow function with contextual typing
const double = (x) => x * 2;// Option 1: annotate parameter
const double = (x: number) => x * 2;
// Option 2: annotate variable
const double2: (x: number) => number = (x) => x * 2;Can you write this from memory?
Define a function `greet` taking `name` as an argument.
Optional and Default Parameters
JavaScript default parameters (function connect(host, port = 5432)) work identically in TypeScript. The compiler infers the type of port from the default value, so port: number is optional if the default is a number literal. TypeScript adds a separate concept: optional parameters marked with ?. Writing function greet(name: string, title?: string) means title can be omitted entirely, in which case its value is undefined. This is subtly different from a default: optional parameters have type string | undefined, while default parameters have the type of the default value. The distinction matters when designing APIs. An optional parameter signals "this might not be provided and you should handle both cases," while a default parameter signals "this always has a value even if the caller does not specify one." JavaScript conflates the two because it treats missing arguments as undefined, but TypeScript's type system distinguishes them. Rest parameters also carry types: (...nums: number[]) collects all remaining arguments into a typed array. The JavaScript ...nums syntax is unchanged; you just add : number[] after the name.
Optional parameter
function greet(name, title) {
if (title) {
return title + " " + name;
}
return name;
}
greet("Ada");
greet("Ada", "Dr.");function greet(name: string, title?: string) {
if (title) {
return `${title} ${name}`;
}
return name;
}
greet("Ada");
greet("Ada", "Dr.");Default parameter
function connect(host, port = 5432) {
console.log(host + ":" + port);
}function connect(host: string, port = 5432) {
console.log(`${host}:${port}`);
// port inferred as number from default
}Can you write this from memory?
Define a function `greet` taking `name` as an argument.
Function Overloads
JavaScript has no function overloading -- you write one implementation and handle different argument shapes with runtime checks. TypeScript adds overload signatures: separate declarations that describe different calling conventions, followed by a single implementation that handles all cases. The overload signatures are the public API; the implementation signature is hidden from callers. This pattern shows up when a function accepts different argument types and returns different result types depending on the input. For example, a createElement function might return HTMLDivElement when called with "div" and HTMLSpanElement when called with "span." Without overloads, the return type would be the broad HTMLElement, losing specific type information at call sites. The implementation body still uses runtime checks (if typeof arg === "string"), which is identical to what you would write in JavaScript. The overload signatures just teach the compiler about the relationship between input and output types. Overloads are a power feature that most migration projects do not need immediately. They solve the specific problem of type-narrowing across argument patterns, and many codebases use union types or generic constraints instead.
Function overloads
function parse(input) {
if (typeof input === "string") {
return JSON.parse(input);
}
return input;
}function parse(input: string): object;
function parse(input: object): object;
function parse(input: string | object): object {
if (typeof input === "string") {
return JSON.parse(input);
}
return input;
}Typing Callbacks and Higher-Order Functions
JavaScript callbacks are just functions passed as arguments. TypeScript types them with function type syntax: (item: string) => boolean describes a function that takes a string and returns a boolean. When you pass callbacks to array methods, TypeScript already knows the element type from the array, so the callback parameters are inferred. Writing [1, 2, 3].filter(n => n > 1) infers n as number without annotation. Where explicit callback types help is in your own higher-order functions: function retry(fn: () => Promise<void>, times: number). Without the type annotation on fn, TypeScript cannot check that the function you pass matches the expected signature. The type (...args: A) => R captures both parameters and return type and can be used in generic higher-order functions to preserve the full signature through wrappers. React developers encounter this daily with event handlers (onClick: (e: React.MouseEvent) => void), component props with render callbacks, and custom hooks that accept configuration functions. The callback typing pattern translates directly from JavaScript -- you add parameter and return annotations -- but the payoff in autocomplete and error detection is immediate.
Callback type in higher-order function
function retry(fn, times) {
for (let i = 0; i < times; i++) {
try { return fn(); }
catch (e) { if (i === times - 1) throw e; }
}
}function retry<T>(
fn: () => T,
times: number
): T {
for (let i = 0; i < times; i++) {
try { return fn(); }
catch (e) { if (i === times - 1) throw e; }
}
throw new Error("unreachable");
}Array method inference
const evens = [1, 2, 3, 4].filter(n => n % 2 === 0);const evens = [1, 2, 3, 4].filter(n => n % 2 === 0);
// n inferred as number, evens inferred as number[]