Video

Watch the full video on YouTube:

Function Typing

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.

TypeScript Guide 05: Unions, Exclude and ExtractNestJS API Development Guide 07: POST, Body and DTO Validation