DEV Community

Cover image for Functions in TypeScript: A Simple Introduction
Alex Devero
Alex Devero

Posted on • Originally published at blog.alexdevero.com

Functions in TypeScript: A Simple Introduction

Functions are fundamental part of JavaScript. They help make code reusable while keeping it flexible. TypeScript takes these benefits further by helping you write type safe functions. This tutorial will show you how to use functions in TypeScript, how to specify types for parameters, return types and more.

Nothing new, just safer?

Functions are among the most frequently used language features. This applies not only to JavaScript and TypeScript, but also to other programming languages. In JavaScript though, there is one problem. JavaScript is not a statically type programming language. Because of this, there are some things you can't do.

For example, when you define a function, you can't specify allowed types for its parameters. This makes it easier to call a function with argument of a wrong type. You can create a function that takes two numbers as parameters and call it passing in two strings as arguments. JavaScript will not stop you.

You also can't specify the return type of a function. This can lead to issues when one part of program calling a function expects to get one thing while the function returns something different. This is where TypeScript can be handy. It is true that TypeScript doesn't really add anything new to functions.

Functions in TypeScript are still functions, those functions you know from JavaScript. What TypeScript does is it makes functions safer, more predictable. It does so by combining functions with its powerful type system. This helps you ensure your functions receive and return the correct types. Let's take a look at how.

Parameter types

When you create a function you can also specify any parameters it accepts. This makes your code more flexible and easier to reuse under different conditions. With TypeScript, you can also specify types for these parameters. When you try to call any function with an argument of a wrong type, TypeScript will warn you.

The syntax for defining types for function parameters is simple. It is composed of colon (:) and the type. First, you define a parameter. Then, you add colon right after it. After that, you specify what type is allowed for that specific parameter.

// Create a function that accepts two parameters: str1 and str2.
// Both parameters must be of type string.
function joinStrings(str1: string, str2: string) {
  return str1 + str2
}

// This will work:
joinStrings('Wo', 'rld')
// Output:
// 'World'

// This will not work:
joinStrings('Hel', 135)
// TS error: Argument of type 'number' is not assignable to parameter of type 'string'.
Enter fullscreen mode Exit fullscreen mode

TypeScript also allows you to specify multiple types. You can achieve this by using union types. This basically means that you can take two or more types and tell TypeScript that any one of these types can be used. When you want to use union types, you specify all possible types and use | to separate them.

// Create a function that accepts one parameter.
// This parameter can be either number, string or boolean.
function numStrBool(val: number | string | boolean) {
  return typeof val
}

// This will work in TypeScript:
numStrBool(15)
// Output:
// 'number'

// This will also work in TypeScript:
numStrBool('Tea')
// Output:
// 'string'

// This will also work in TypeScript:
numStrBool(true)
// Output:
// 'boolean'

// This will not work in TypeScript:
numStrBool({ key: 15 })
// TS error: Argument of type '{ key: number; }' is not assignable to parameter of type 'string | number | boolean'.
Enter fullscreen mode Exit fullscreen mode

Optional parameters

Just like in JavaScript, you can also mark some parameters as optional in TypeScript. The syntax is the same. In order to mark a parameter as optional you add question mark (?) right after it. When you work with functions in TypeScript, the question mark goes after the parameter name and before the colon and type.

// Create a function that accepts two parameters,
// the first one required and the second optional:
function combineWords(word1: string, word2?: string) {
  return `${word1}${word2 ? word2 : ''}`
}

// This will work in TypeScript:
combineWords('Hello', 'world')
// Output:
// 'Helloworld'

// This will also work in TypeScript:
combineWords('Say', 'Bye')
// Output:
// 'SayBye'

// This will also work in TypeScript:
combineWords('Hi')
// Output:
// 'Hi'

// This will not work in TypeScript:
combineWords()
// TS error: Expected 1-2 arguments, but got 0.
Enter fullscreen mode Exit fullscreen mode

Default parameters

TypeScript also allows you to define default values for function parameters. That's not all. When you define default values for functions in TypeScript, you don't have to specify types for these parameters explicitly. TypeScript will be able to use default values to infer types for parameters automatically. Less work for you.

The syntax for default values is the same as in JavaScript. You specify a parameter and right after it add an equal sign with the default value. If you also want to explicitly specify a type for the parameter, you specify the type first. This means using following order: parameter name, colon and type, equal sign and default value.

// Example 1: default parameters and implicit type:
// Create a function that accepts two parameters.
// Both parameters are of type numbers,
// the second has default value 0.
function addTwoNumbers(num1: number, num2 = 0) {
  return num1 + num2
}
// TS will add type for num2 implicitly:
// addTwoNumbers(num1: number, num2: number = 0)

// This will work in TypeScript:
addTwoNumbers(98, 656)
// Output:
// 754

// This will also work in TypeScript:
addTwoNumbers(63)
// Output:
// 63

// This will not work in TypeScript:
addTwoNumbers('13')
// TS error: Argument of type 'string' is not assignable to parameter of type 'number'.


// Example 2: default parameters and explicit type:
function addTwoNumbers(num1: number, num2: number = 0) {
  return num1 + num2
}
Enter fullscreen mode Exit fullscreen mode

Rest parameters

Some functions may accept an arbitrary number of arguments. This is where rest parameters syntax will be very useful. In TypeScript, you can specify type for rest parameters just as easily as any other parameters. The syntax is the same. You add the rest parameter and follow it with colon and the type.

When you work with rest parameters, remember one thing. Parameters passed as rest parameters will be collected in an array. This means that you also have to use the array type, either type[] or Array<type>.

// Create a function that accepts two parameters.
// The first parameter is a string.
// The second is a rest parameter and is an array of strings.
function buildATeam(leader: string, ...teammates: string[]) {
  return `Team is composed of ${leader} as a the team leader and ${teammates.length > 0 ? teammates.join(', ') : 'nobody'} as the core team.`
}

// This will work in TypeScript:
buildATeam('Joe', 'Vicky', 'Thomas')
// Output:
// 'Team is composed of Joe as a the team leader and Vicky, Thomas as the core team.'

// This will also work in TypeScript:
buildATeam('Sandra', 'Alex', 'Jack', 'Victor')
// Output:
// 'Team is composed of Sandra as a the team leader and Alex, Jack, Victor as the core team.'

// This will also work in TypeScript:
buildATeam('Stuart')
// Output:
// 'Team is composed of Stuart as a the team leader and nobody as the core team.'

// This will not work in TypeScript:
buildATeam(13, 15)
// TS error: Argument of type 'number' is not assignable to parameter of type 'string'.
Enter fullscreen mode Exit fullscreen mode

Return types

TypeScript also allows you to specify the return type of a function. That said, you will not find yourself doing this very often. In most cases, TypeScript will be able to infer the function's return type based on return statements inside the function itself. You don't have to specify these types explicitly.

What if TypeScript is not able to infer the return type or you want to specify by yourself? In order to specify function return type you have to add colon and the type right after the parameter list, before the function body.

// Create a function that accepts two parameters.
// Both parameters are numbers.
// Set the return type to be a number (the '): number {' part).
function numToPow(num: number, exp: number): number {
  return num ** exp
}

// This will work in TypeScript:
numToPow(15, 8)
// Output:
// 2562890625

// This will not work in TypeScript:
numToPow(12, '9')
// TS error: Argument of type 'string' is not assignable to parameter of type 'number'.
Enter fullscreen mode Exit fullscreen mode

Function types

When you work with functions in TypeScript you usually define all its types when you declare a function. What TypeScript also allows is to define a function signature before you actually create the function itself. Later, when you create the function, TypeScript will use the signature to infer all types for you automatically.

This signature is called a function type. It can be useful when you expect a specific function signature. This function type is composed of two parts: parameters and return type. When you declare a function type, you have to specify both these parts.

// Create function type for function subtract.
// This function accepts two parameters, both are numbers.
// Its return type is also a number.
let subtract: (a: number, b: number) => number

// Assign the actual function to the variable:
subtract = function (a, b) {
  return a - b
}

// This will not work in TypeScript:
subtract = function (a: string, b: string) {
  return a + b
}
// TS error: Type '(a: string, b: string) => string' is not assignable to type '(a: number, b: number) => number'.
Enter fullscreen mode Exit fullscreen mode

Remember that when you declare the function type, you don't have to specify the types again when you assign the actual function. TypeScript will infer all types implicitly based on the function type you previously created.

// Create function type for function subtract:
let subtract: (a: number, b: number) => number

// These types are redundant:
subtract = function (a: number, b: number) {
  return a - b
}
Enter fullscreen mode Exit fullscreen mode

There is another way to achieve this. You can also create a function type during the function assignment. This allows you to combine both steps into one.

// Create function with function:
let subtract: (a: number, b: number) => number = function (a: number, b: number) {
  return a - b
}
Enter fullscreen mode Exit fullscreen mode

Function types with types and interfaces

Those ways to create function type we just discussed have a downside. They are not really re-usable. You create a function type, you assign it a function and you are done. You can't use that type again for another function, unless you copy the code. Luckily, there is another way, two actually.

You can also create function types with types and interfaces. The benefit of both is the already mentioned re-usability. Type and interface will not cease to exist after you use them. When you create function type with a type or interface you can use it whenever you want and as many times as you want.

The syntax for type to declare a function type is very similar to the syntax you saw above. There are only two differences. First, you will replace the variable keyword with type keyword. Second, you will replace the colon after variable name with equal sign. You are basically creating a new type and assign it a function signature.

When you want to use your new type, you use it along with colons, when you create new function expression.

// Function type with variable:
let subtract: (a: number, b: number) => number

// Function type with type:
type TakeNumsReturnNums = (a: number, b: number) => number

// Use the "TakeNumsReturnNums" type with function expression:
const subtract: TakeNumsReturnNums = (a, b) => a - b

// This will work in TypeScript:
subtract(97, 13)
// Output:
// 84

// This will not work in TypeScript:
subtract(55, '15')
// TS error: Argument of type 'string' is not assignable to parameter of type 'number'.


// Use the type again:
const sumNumbers: TakeNumsReturnNums = (x, y) => x + y

// And again:
const divideNumbers: TakeNumsReturnNums = (c, d) => c / d
Enter fullscreen mode Exit fullscreen mode

Doing the same with an interface requires different syntax. You create a new interface. Inside it, you will specify the types for parameter list as the key and return type as the value. Then, you use it in the same way as the type.

// Create interface for subtract function:
interface TakeNumsReturnNums {
  // Syntax:
  // (types for parameter list): return type;
  (a: number, b: number): number;
}

// Use the "TakeNumsReturnNums" interface with function expression:
const subtract: TakeNumsReturnNums = (a, b) => a - b

// This will work in TypeScript:
subtract(55, 21)
// Output:
// 34

// This will not work in TypeScript:
subtract(true, 66)
// TS error: Argument of type 'boolean' is not assignable to parameter of type 'number'.

// Use the type again:
const sumNumbers: TakeNumsReturnNums = (x, y) => x + y

// And again:
const divideNumbers: TakeNumsReturnNums = (c, d) => c / d
Enter fullscreen mode Exit fullscreen mode

Conclusion: Functions in TypeScript

TypeScript makes it easy to write functions that are type safe and more predictable. I hope that this tutorial helped you understand how to use functions in TypeScript. How to define types for parameters and return types and how to create function types.

Top comments (0)