
TypeScript Guide 06: Function Typing
Video
Watch the full video on YouTube:
Introduction
In this episode of the TypeScript series, we continue with advanced typing. Previously we saw Exclude and Extract, and now we’ll learn how to type functions to process and create new data types efficiently.
Traditional vs Arrow Functions
In JavaScript and TypeScript there are two main ways to create functions:
Traditional Function
function traditional(): string {
return "Hello"
}
Arrow Function
const arrow = (): string => {
return "Hello"
}
Both are perfectly valid and are typed in a similar way.
Typing Return Values
To specify what data type a function returns, we use the colon after the parentheses:
function traditional(): string {
return 1 // Error: The number is not assignable to type string
}
const arrow = (): string => {
return 1 // Error: The number is not assignable to type string
}
If we specify a return type that isn’t undefined, void or any, TypeScript forces us to return a value.
Typing Parameters
Parameters are typed similarly to variables:
function withParams(name: string): string {
return `Hello ${name}`
}
const arrowWithParams = (value: number): string => {
return `Value: ${value}`
}
Multiple Arguments
Fixed Number of Arguments
function multipleArguments(a: number, b: string): void {
// Doesn't return anything
}
Unknown Number of Arguments (Spread Operator)
For functions that receive an undefined number of arguments, we use the spread operator:
function multipleArguments(...values: number[]): void {
// values is a number array
console.log(values)
}
// Can be called with any number of arguments
multipleArguments(1, 2, 3)
multipleArguments(1, 2, 3, 4, 5)
If we try to pass more arguments than defined in a regular function, TypeScript shows an error:
function fixedArgs(a: number, b: number): void {}
fixedArgs(1, 2, 3, 4) // Error: Expected 2 arguments, but 4 were received
Functions That Don’t Return Anything (void)
We can specify that a function doesn’t return anything using void:
function doNotReturn(): void {
console.log("I don't return anything")
}
Or simply omit the return type:
function doNotReturn() {
console.log("I don't return anything")
}
Higher-Order Functions (Closures)
Functions can return other functions, creating what’s known as closures:
const closure = (outside: string) => {
return (inside: number) => {
console.log(outside, inside)
}
}
const myClosure = closure("Hello")
myClosure(42)
Typing Closures
const closure = (outside: string) => {
return (inside: boolean) => {
console.log(outside, inside)
}
}
TypeScript automatically infers the type of parameters in the inner function based on the return signature.
Typing Functions with function
There’s an important difference between using function as a type and creating specific function signatures:
// function as a general type
const randomFunction: Function = () => {}
// Specific function signature
type ReturningFunction = () => string
const myFunction: ReturningFunction = () => {
return "Hello"
}
Difference Between function and Specific Signatures
// With function as type (general)
const randomFunction: Function = () => {}
randomFunction(1, 2, 3, 4) // No error shown, but it's unsafe
const result = randomFunction() // result is type any
// With specific signature
type SpecificFunction = (a: number) => string
const specificFunction: SpecificFunction = (a) => {
return `Number: ${a}`
}
specificFunction("text") // Error: The string is not assignable to type number
Function Signatures Defined Separately
We can define a function signature first and then assign it:
type ReturningFunction = () => string
const signedFunction: ReturningFunction = () => {
return "Hello" // Correct
}
const wrongFunction: ReturningFunction = () => {} // Error: Must return a string
Signatures with Parameters
type ReturningFunctionWithParameters = (a: number, b: boolean) => void
const example: ReturningFunctionWithParameters = (a, b) => {
console.log(a, b) // a is number, b is boolean
}
// Error if more arguments are added
const wrong: ReturningFunctionWithParameters = (a, b, c) => {} // Error: 3 arguments
Nested Functions
Functions can also return nested functions with different return types:
type FunctionWithReturningFunction = () => () => void
// Or more deeply nested
type NestedFunction = () => () => () => number
const nested: NestedFunction = () => {
return () => {
return () => {
return 42
}
}
}
Receiving Objects, Arrays, and Callbacks
Objects
function withObject(obj: Record<string, string>) {
console.log(obj)
}
withObject({ key: "value" })
Arrays
function withArray(arr: string[]) {
console.log(arr)
}
withArray(["a", "b", "c"])
Callbacks
function withCallback(callback: (a: string) => void) {
callback("Hello")
}
withCallback((a) => {
console.log(a)
})
Mixing Types
function mixed(records: Record<string, string>[]) {
console.log(records)
}
Conclusion
Function typing is fundamental in TypeScript for ensuring type safety in our code. From simple parameter and return value typing, to complex function signatures and closures, TypeScript provides us with the tools to create more robust and maintainable code.
