Function basics
In this chapter, you learn the basics of writing functions in TypeScript.
What you learn in this chapter
- How to write type annotations for functions
- The syntax and usage of arrow functions
- The basics of function expressions
- Default parameters and optional parameters
- void-type functions
You can try the code examples in this chapter on the TypeScript Playground.
Type annotations for functions
In TypeScript, you can specify types for a function's parameters and return value. This makes the function's usage clear and catches mistakes at the call site at compile time.
Basic syntax
// The basic syntax of a function type annotation
function functionName(parameterName: parameterType): returnType {
// processing
return returnValue;
}
A concrete example
// A function that adds two numbers
function add(a: number, b: number): number {
return a + b;
}
// Usage examples
console.log(add(5, 3)); // 8
console.log(add(10, 20)); // 30
// An example of a type error (an error at compile time)
// console.log(add('5', '3'));
// Error: Argument of type 'string' is not assignable to parameter of type 'number'
Code explanation:
a: number- the first parameterais of type numberb: number- the second parameterbis of type number: number(after the function name) - the return type is number- A call with mismatched types is a compile error
Inference of the return type
TypeScript can infer the return type automatically.
// Specify the return type explicitly
function add1(a: number, b: number): number {
return a + b;
}
// Omit the return type (leave it to inference)
function add2(a: number, b: number) {
return a + b; // the return type is inferred as number
}
// Both work the same way
console.log(add1(5, 3)); // 8
console.log(add2(5, 3)); // 8
Best practices:
// Recommended: always add type annotations to parameters
function multiply(a: number, b: number) {
return a * b;
}
// For complex functions, also state the return type explicitly (improves readability)
function complexCalculation(x: number, y: number): number {
const step1 = x * 2;
const step2 = y + 10;
const result = step1 + step2;
return result;
}
// Not recommended: parameters become the any type without type annotations
function bad(a, b) { // a and b are of type any
return a + b;
}
void-type functions
A function with no return value returns the void type.
// A function with no return value (it just prints to the console)
function greet(name: string): void {
console.log(`Hello, ${name}!`);
// no return statement, or just return;
}
greet('Alice'); // 'Hello, Alice!'
// You cannot use the return value of a function that returns void
const result = greet('Bob');
console.log(result); // undefined
Practical examples of void:
// A function that saves data (no return value needed)
function saveData(data: string): void {
localStorage.setItem('userData', data);
console.log('Data saved successfully');
}
// A function that prints a log
function log(level: string, message: string): void {
const timestamp = new Date().toISOString();
console.log(`[${timestamp}] [${level}] ${message}`);
}
log('INFO', 'Application started');
log('ERROR', 'Something went wrong');
Arrow functions
A concise way to write functions, introduced in ES2015. Modern TypeScript code uses them frequently.
Basic syntax
// A traditional function expression
const add1 = function(a: number, b: number): number {
return a + b;
};
// An arrow function (same feature)
const add2 = (a: number, b: number): number => {
return a + b;
};
// An arrow function (short form: when there is a single expression)
const add3 = (a: number, b: number): number => a + b;
// All produce the same result
console.log(add1(5, 3)); // 8
console.log(add2(5, 3)); // 8
console.log(add3(5, 3)); // 8
The short form of arrow functions
// The normal way
const square1 = (x: number): number => {
return x * x;
};
// Short form: omit the braces and return (when there is a single expression)
const square2 = (x: number): number => x * x;
// Parentheses are required when there is a type annotation
const square3 = (x: number) => x * x;
// Parentheses are required when there are zero parameters
const getRandom = (): number => Math.random();
console.log(square1(5)); // 25
console.log(square2(5)); // 25
console.log(square3(5)); // 25
console.log(getRandom()); // 0.123... (a random value)
When returning an object
// When returning an object, wrap it in parentheses
const createUser = (name: string, age: number) => ({
name: name,
age: age
});
// Or the normal way with braces
const createUser2 = (name: string, age: number) => {
return {
name: name,
age: age
};
};
console.log(createUser('Alice', 25)); // { name: 'Alice', age: 25 }
console.log(createUser2('Bob', 30)); // { name: 'Bob', age: 30 }
When returning an object literal directly, you must wrap it in parentheses; otherwise the braces are interpreted as the function block and it errors.
Combining with array methods
Arrow functions are often used with array methods.
const numbers = [1, 2, 3, 4, 5];
// map: double each element
const doubled = numbers.map((n) => n * 2);
console.log(doubled); // [2, 4, 6, 8, 10]
// filter: extract only the even numbers
const evens = numbers.filter((n) => n % 2 === 0);
console.log(evens); // [2, 4]
// reduce: calculate the sum
const sum = numbers.reduce((acc, n) => acc + n, 0);
console.log(sum); // 15
// find: get the first element that matches a condition
const found = numbers.find((n) => n > 3);
console.log(found); // 4
Function expressions
A way to assign a function to a variable.
A basic function expression
// The basic syntax of a function expression
const add = function(a: number, b: number): number {
return a + b;
};
console.log(add(5, 3)); // 8
A type annotation for a function
You can also state a function's type explicitly on the variable.
// A type annotation on the variable (states the function's type)
const multiply: (a: number, b: number) => number = function(a, b) {
return a * b;
};
console.log(multiply(4, 5)); // 20
A variable of a function type
// Define a function's type
// Syntax: (parameter: type, ...) => returnType
let operation: (a: number, b: number) => number;
// You can assign a function that matches this type
operation = function(a, b) {
return a + b;
};
console.log(operation(10, 20)); // 30
// Assign a different function
operation = function(a, b) {
return a * b;
};
console.log(operation(10, 20)); // 200
Default parameters
You can set a default value for a parameter. When the argument is omitted, the default value is used.
// The basics of default parameters
function greet(name: string, greeting: string = 'Hello'): string {
return `${greeting}, ${name}!`;
}
console.log(greet('Alice')); // 'Hello, Alice!'
console.log(greet('Bob', 'Hi')); // 'Hi, Bob!'
console.log(greet('Charlie', 'Hey')); // 'Hey, Charlie!'
A practical example
function createUser(
name: string,
age: number = 0,
isActive: boolean = true
) {
return { name, age, isActive };
}
console.log(createUser('Alice'));
// { name: 'Alice', age: 0, isActive: true }
console.log(createUser('Bob', 25));
// { name: 'Bob', age: 25, isActive: true }
console.log(createUser('Charlie', 30, false));
// { name: 'Charlie', age: 30, isActive: false }
Default parameters and type inference
// The type is inferred from the default value
function multiply(a: number, b = 2) { // the type of b is inferred as number
return a * b;
}
console.log(multiply(5)); // 10 (5 * 2)
console.log(multiply(5, 3)); // 15 (5 * 3)
Optional parameters
Adding ? after a parameter name makes that parameter optional.
// Basic usage
function greet(name: string, greeting?: string): string {
if (greeting) {
return `${greeting}, ${name}!`;
}
return `Hello, ${name}!`;
}
console.log(greet('Alice')); // 'Hello, Alice!'
console.log(greet('Bob', 'Good morning')); // 'Good morning, Bob!'
A note on optional parameters
// Place optional parameters after the required ones
// Correct: the optional parameters come last
function correct(a: number, b?: number, c?: number) {
console.log(a, b, c);
}
correct(1); // 1 undefined undefined
correct(1, 2); // 1 2 undefined
correct(1, 2, 3); // 1 2 3
// Wrong: a required parameter comes after an optional one
// function wrong(a?: number, b: number) {} // Error
The type of an optional parameter
The type of an optional parameter automatically has | undefined added.
function printValue(value?: string): void {
// the type of value is string | undefined
console.log(value);
// an existence check is needed before use
// console.log(value.toUpperCase()); // Error: 'value' is possibly 'undefined'
// the correct usage
if (value) {
console.log(value.toUpperCase());
}
// or optional chaining
console.log(value?.toUpperCase());
}
printValue(); // undefined, undefined
printValue('hello'); // hello, HELLO, HELLO
Default parameters vs. optional parameters
| Characteristic | Default parameter | Optional parameter |
|---|---|---|
| Syntax | param = value | param? |
| Value when omitted | the specified default value | undefined |
| undefined check | not needed | needed |
| Use case | usually use a specific value | a value may or may not be present |
// Default parameter: use 'Hello' when omitted
function greet1(name: string, greeting: string = 'Hello'): string {
return `${greeting}, ${name}!`; // greeting is always a string
}
// Optional parameter: greeting may be undefined
function greet2(name: string, greeting?: string): string {
if (greeting) { // an undefined check is needed
return `${greeting}, ${name}!`;
}
return `Hello, ${name}!`;
}
Try it: build a calculation utility ★
Create a set of calculation utility functions that meet the following requirements.
Requirements:
add(a, b): a function that adds two numberssubtract(a, b): a function that subtracts two numbersmultiply(a, b): a function that multiplies two numbers (the default value ofbis 1)divide(a, b): a function that divides two numbers (returns null whenbis 0)calculate(a, b, operation): a function that takes two numbers and a kind of operation ('add', 'subtract', 'multiply', 'divide') and returns the result
Hint
- Add type annotations to the parameters and return value of every function
- Use a default parameter in
multiply - In
divide, check for division by zero, and make the return typenumber | null - In
calculate, branch on the value ofoperation
Answer and explanation
// A function that adds two numbers
const add = (a: number, b: number): number => a + b;
// A function that subtracts two numbers
const subtract = (a: number, b: number): number => a - b;
// A function that multiplies two numbers (the default value of b is 1)
const multiply = (a: number, b: number = 1): number => a * b;
// A function that divides two numbers (returns null when b is 0)
const divide = (a: number, b: number): number | null => {
if (b === 0) {
return null; // prevent division by zero
}
return a / b;
};
// A function that takes two numbers and a kind of operation and returns the result
const calculate = (
a: number,
b: number,
operation: 'add' | 'subtract' | 'multiply' | 'divide'
): number | null => {
switch (operation) {
case 'add':
return add(a, b);
case 'subtract':
return subtract(a, b);
case 'multiply':
return multiply(a, b);
case 'divide':
return divide(a, b);
default:
return null;
}
};
// Tests
console.log(add(5, 3)); // 8
console.log(subtract(10, 4)); // 6
console.log(multiply(5, 3)); // 15
console.log(multiply(5)); // 5 (default value 1)
console.log(divide(10, 2)); // 5
console.log(divide(10, 0)); // null
console.log(calculate(10, 5, 'add')); // 15
console.log(calculate(10, 5, 'subtract')); // 5
console.log(calculate(10, 5, 'multiply')); // 50
console.log(calculate(10, 5, 'divide')); // 2
console.log(calculate(10, 0, 'divide')); // null
Explanation:
add,subtract: basic functions that use the short form of arrow functionsmultiply: uses the default parameterb = 1to make the second argument optionaldivide: makes the return typenumber | nullto guard against division by zerocalculate: uses the literal-type union'add' | 'subtract' | 'multiply' | 'divide'to accept only valid operations
Summary
What you learned in this chapter:
- Type annotations for functions: specifying types for parameters and the return value lets you define type-safe functions
- Arrow functions: a concise way to write functions with
=>, which pairs well with array methods - Function expressions: assign a function to a variable and state the function's type explicitly
- Default parameters: set the default value used when omitted with
param = value - Optional parameters: define parameters that can be omitted with
param? - The void type: represents a function with no return value
In the next chapter, you will learn advanced function topics such as function overloads, callback functions, and rest parameters.
Common pitfalls for beginners
Common mistakes
❌ Forgetting the parentheses when returning an object from an arrow function
// ❌ Wrong: the braces are interpreted as the function block
const createUser = (name: string) => { name: name };
// This does not return { name: name };
// it is interpreted as an expression statement with a label called name:
// ✅ Correct: wrap it in parentheses to return it as an object literal
const createUser = (name: string) => ({ name: name });
// Or the normal way
const createUser2 = (name: string) => {
return { name: name };
};
Cause: In JavaScript, {} is used for both function blocks and object literals.
Solution: When you return an object directly, wrap it in ({}).
❌ Confusing default parameters with optional parameters
// ❌ Wrong: adding optional (?) when there is already a default value
function greet(name?: string = 'Guest'): void { // Error
console.log(`Hello, ${name}!`);
}
// ✅ Correct: an optional marker is unnecessary when there is a default value
function greet(name: string = 'Guest'): void {
console.log(`Hello, ${name}!`); // name is always a string
}
// When you use an optional parameter (it may be undefined)
function greet2(name?: string): void {
console.log(`Hello, ${name ?? 'Guest'}!`); // name is string | undefined
}
Cause: A default parameter automatically becomes optional, so ? is redundant.
Solution: When you set a default value, do not add ?; use only = value.
❌ Placing an optional parameter before a required one
// ❌ Wrong: the optional parameter comes first
function wrong(a?: number, b: number): number {
// Error: A required parameter cannot follow an optional parameter
return (a ?? 0) + b;
}
// ✅ Correct: place the required parameter first
function correct(b: number, a?: number): number {
return (a ?? 0) + b;
}
Cause: Because JavaScript function calls use positional arguments, optional parameters must come last.
Solution: Always place optional parameters after the required ones.
❌ Forgetting the return type annotation, so it becomes any
// ❌ Wrong: when noImplicitAny is disabled, the parameters become the any type
function add(a, b) { // a and b are of type any
return a + b;
}
add('5', '10'); // '510' (string concatenation, unintended behavior)
// ✅ Correct: add type annotations to the parameters
function add(a: number, b: number): number {
return a + b;
}
// add('5', '10'); // Error: caught as a type error
Cause: When TypeScript's noImplicitAny option is disabled, parameters without type annotations become any.
Solution: Set noImplicitAny: true in tsconfig.json, and always add type annotations.
❌ Trying to use the return value of a void function
function logMessage(msg: string): void {
console.log(msg);
}
// ❌ Wrong: trying to use the return value of a void function
const result = logMessage('Hello');
if (result) { // result is void (undefined)
console.log('Success'); // never runs
}
// ✅ Correct: expect a void function to only have side effects
logMessage('Hello');
// When you need a return value, define the appropriate type
function formatMessage(msg: string): string {
return `[INFO] ${msg}`;
}
const formatted = formatMessage('Hello');
console.log(formatted); // '[INFO] Hello'
Cause: A void-type function returns no value, so its return value is undefined.
Solution: When you need a return value, define the appropriate return type.