Was this page helpful?

More on Functions

Functions are the basic building block of any application, whether they’re local functions, imported from another module, or methods on a class.They’re also values, and just like other values, TypeScript has many ways to describe how functions can be called.Let’s learn about how to write types that describe functions.

Function Type Expressions

The simplest way to describe a function is with afunction type expression.These types are syntactically similar to arrow functions:

ts
functiongreeter(fn: (a:string)=>void) {
fn("Hello, World");
}
 
functionprintToConsole(s:string) {
console.log(s);
}
 
greeter(printToConsole);
Try

The syntax(a: string) => void means “a function with one parameter, nameda, of typestring, that doesn’t have a return value”.Just like with function declarations, if a parameter type isn’t specified, it’s implicitlyany.

Note that the parameter name isrequired. The function type(string) => void means “a function with a parameter namedstring of typeany“!

Of course, we can use a type alias to name a function type:

ts
typeGreetFunction = (a:string)=>void;
functiongreeter(fn:GreetFunction) {
// ...
}
Try

Call Signatures

In JavaScript, functions can have properties in addition to being callable.However, the function type expression syntax doesn’t allow for declaring properties.If we want to describe something callable with properties, we can write acall signature in an object type:

ts
typeDescribableFunction = {
description:string;
(someArg:number):boolean;
};
functiondoSomething(fn:DescribableFunction) {
console.log(fn.description +" returned " +fn(6));
}
 
functionmyFunc(someArg:number) {
returnsomeArg >3;
}
myFunc.description ="default description";
 
doSomething(myFunc);
Try

Note that the syntax is slightly different compared to a function type expression - use: between the parameter list and the return type rather than=>.

Construct Signatures

JavaScript functions can also be invoked with thenew operator.TypeScript refers to these asconstructors because they usually create a new object.You can write aconstruct signature by adding thenew keyword in front of a call signature:

ts
typeSomeConstructor = {
new (s:string):SomeObject;
};
functionfn(ctor:SomeConstructor) {
returnnewctor("hello");
}
Try

Some objects, like JavaScript’sDate object, can be called with or withoutnew.You can combine call and construct signatures in the same type arbitrarily:

ts
interfaceCallOrConstruct {
(n?:number):string;
new (s:string):Date;
}
 
functionfn(ctor:CallOrConstruct) {
// Passing an argument of type `number` to `ctor` matches it against
// the first definition in the `CallOrConstruct` interface.
console.log(ctor(10));
(parameter) ctor: CallOrConstruct(n?: number) => string
 
// Similarly, passing an argument of type `string` to `ctor` matches it
// against the second definition in the `CallOrConstruct` interface.
console.log(newctor("10"));
(parameter) ctor: CallOrConstructnew (s: string) => Date
}
 
fn(Date);
Try

Generic Functions

It’s common to write a function where the types of the input relate to the type of the output, or where the types of two inputs are related in some way.Let’s consider for a moment a function that returns the first element of an array:

ts
functionfirstElement(arr:any[]) {
returnarr[0];
}
Try

This function does its job, but unfortunately has the return typeany.It’d be better if the function returned the type of the array element.

In TypeScript,generics are used when we want to describe a correspondence between two values.We do this by declaring atype parameter in the function signature:

ts
functionfirstElement<Type>(arr:Type[]):Type |undefined {
returnarr[0];
}
Try

By adding a type parameterType to this function and using it in two places, we’ve created a link between the input of the function (the array) and the output (the return value).Now when we call it, a more specific type comes out:

ts
// s is of type 'string'
consts =firstElement(["a","b","c"]);
// n is of type 'number'
constn =firstElement([1,2,3]);
// u is of type undefined
constu =firstElement([]);
Try

Inference

Note that we didn’t have to specifyType in this sample.The type wasinferred - chosen automatically - by TypeScript.

We can use multiple type parameters as well.For example, a standalone version ofmap would look like this:

ts
functionmap<Input,Output>(arr:Input[],func: (arg:Input)=>Output):Output[] {
returnarr.map(func);
}
 
// Parameter 'n' is of type 'string'
// 'parsed' is of type 'number[]'
constparsed =map(["1","2","3"], (n)=>parseInt(n));
Try

Note that in this example, TypeScript could infer both the type of theInput type parameter (from the givenstring array), as well as theOutput type parameter based on the return value of the function expression (number).

Constraints

We’ve written some generic functions that can work onany kind of value.Sometimes we want to relate two values, but can only operate on a certain subset of values.In this case, we can use aconstraint to limit the kinds of types that a type parameter can accept.

Let’s write a function that returns the longer of two values.To do this, we need alength property that’s a number.Weconstrain the type parameter to that type by writing anextends clause:

ts
functionlongest<Typeextends {length:number }>(a:Type,b:Type) {
if (a.length >=b.length) {
returna;
}else {
returnb;
}
}
 
// longerArray is of type 'number[]'
constlongerArray =longest([1,2], [1,2,3]);
// longerString is of type 'alice' | 'bob'
constlongerString =longest("alice","bob");
// Error! Numbers don't have a 'length' property
constnotOK =longest(10,100);
Argument of type 'number' is not assignable to parameter of type '{ length: number; }'.2345Argument of type 'number' is not assignable to parameter of type '{ length: number; }'.
Try

There are a few interesting things to note in this example.We allowed TypeScript toinfer the return type oflongest.Return type inference also works on generic functions.

Because we constrainedType to{ length: number }, we were allowed to access the.length property of thea andb parameters.Without the type constraint, we wouldn’t be able to access those properties because the values might have been some other type without a length property.

The types oflongerArray andlongerString were inferred based on the arguments.Remember, generics are all about relating two or more values with the same type!

Finally, just as we’d like, the call tolongest(10, 100) is rejected because thenumber type doesn’t have a.length property.

Working with Constrained Values

Here’s a common error when working with generic constraints:

ts
functionminimumLength<Typeextends {length:number }>(
obj:Type,
minimum:number
):Type {
if (obj.length >=minimum) {
returnobj;
}else {
return {length:minimum };
Type '{ length: number; }' is not assignable to type 'Type'. '{ length: number; }' is assignable to the constraint of type 'Type', but 'Type' could be instantiated with a different subtype of constraint '{ length: number; }'.2322Type '{ length: number; }' is not assignable to type 'Type'. '{ length: number; }' is assignable to the constraint of type 'Type', but 'Type' could be instantiated with a different subtype of constraint '{ length: number; }'.
}
}
Try

It might look like this function is OK -Type is constrained to{ length: number }, and the function either returnsType or a value matching that constraint.The problem is that the function promises to return thesame kind of object as was passed in, not justsome object matching the constraint.If this code were legal, you could write code that definitely wouldn’t work:

ts
// 'arr' gets value { length: 6 }
constarr =minimumLength([1,2,3],6);
// and crashes here because arrays have
// a 'slice' method, but not the returned object!
console.log(arr.slice(0));
Try

Specifying Type Arguments

TypeScript can usually infer the intended type arguments in a generic call, but not always.For example, let’s say you wrote a function to combine two arrays:

ts
functioncombine<Type>(arr1:Type[],arr2:Type[]):Type[] {
returnarr1.concat(arr2);
}
Try

Normally it would be an error to call this function with mismatched arrays:

ts
constarr =combine([1,2,3], ["hello"]);
Type 'string' is not assignable to type 'number'.2322Type 'string' is not assignable to type 'number'.
Try

If you intended to do this, however, you could manually specifyType:

ts
constarr =combine<string |number>([1,2,3], ["hello"]);
Try

Guidelines for Writing Good Generic Functions

Writing generic functions is fun, and it can be easy to get carried away with type parameters.Having too many type parameters or using constraints where they aren’t needed can make inference less successful, frustrating callers of your function.

Push Type Parameters Down

Here are two ways of writing a function that appear similar:

ts
functionfirstElement1<Type>(arr:Type[]) {
returnarr[0];
}
 
functionfirstElement2<Typeextendsany[]>(arr:Type) {
returnarr[0];
}
 
// a: number (good)
consta =firstElement1([1,2,3]);
// b: any (bad)
constb =firstElement2([1,2,3]);
Try

These might seem identical at first glance, butfirstElement1 is a much better way to write this function.Its inferred return type isType, butfirstElement2’s inferred return type isany because TypeScript has to resolve thearr[0] expression using the constraint type, rather than “waiting” to resolve the element during a call.

Rule: When possible, use the type parameter itself rather than constraining it

Use Fewer Type Parameters

Here’s another pair of similar functions:

ts
functionfilter1<Type>(arr:Type[],func: (arg:Type)=>boolean):Type[] {
returnarr.filter(func);
}
 
functionfilter2<Type,Funcextends (arg:Type)=>boolean>(
arr:Type[],
func:Func
):Type[] {
returnarr.filter(func);
}
Try

We’ve created a type parameterFunc thatdoesn’t relate two values.That’s always a red flag, because it means callers wanting to specify type arguments have to manually specify an extra type argument for no reason.Func doesn’t do anything but make the function harder to read and reason about!

Rule: Always use as few type parameters as possible

Type Parameters Should Appear Twice

Sometimes we forget that a function might not need to be generic:

ts
functiongreet<Strextendsstring>(s:Str) {
console.log("Hello, " +s);
}
 
greet("world");
Try

We could just as easily have written a simpler version:

ts
functiongreet(s:string) {
console.log("Hello, " +s);
}
Try

Remember, type parameters are forrelating the types of multiple values.If a type parameter is only used once in the function signature, it’s not relating anything.This includes the inferred return type; for example, ifStr was part of the inferred return type ofgreet, it would be relating the argument and return types, so would be usedtwice despite appearing only once in the written code.

Rule: If a type parameter only appears in one location, strongly reconsider if you actually need it

Optional Parameters

Functions in JavaScript often take a variable number of arguments.For example, thetoFixed method ofnumber takes an optional digit count:

ts
functionf(n:number) {
console.log(n.toFixed());// 0 arguments
console.log(n.toFixed(3));// 1 argument
}
Try

We can model this in TypeScript by marking the parameter asoptional with?:

ts
functionf(x?:number) {
// ...
}
f();// OK
f(10);// OK
Try

Although the parameter is specified as typenumber, thex parameter will actually have the typenumber | undefined because unspecified parameters in JavaScript get the valueundefined.

You can also provide a parameterdefault:

ts
functionf(x =10) {
// ...
}
Try

Now in the body off,x will have typenumber because anyundefined argument will be replaced with10.Note that when a parameter is optional, callers can always passundefined, as this simply simulates a “missing” argument:

ts
// All OK
f();
f(10);
f(undefined);
Try

Optional Parameters in Callbacks

Once you’ve learned about optional parameters and function type expressions, it’s very easy to make the following mistakes when writing functions that invoke callbacks:

ts
functionmyForEach(arr:any[],callback: (arg:any,index?:number)=>void) {
for (leti =0;i <arr.length;i++) {
callback(arr[i],i);
}
}
Try

What people usually intend when writingindex? as an optional parameter is that they want both of these calls to be legal:

ts
myForEach([1,2,3], (a)=>console.log(a));
myForEach([1,2,3], (a,i)=>console.log(a,i));
Try

What thisactually means is thatcallback might get invoked with one argument.In other words, the function definition says that the implementation might look like this:

ts
functionmyForEach(arr:any[],callback: (arg:any,index?:number)=>void) {
for (leti =0;i <arr.length;i++) {
// I don't feel like providing the index today
callback(arr[i]);
}
}
Try

In turn, TypeScript will enforce this meaning and issue errors that aren’t really possible:

ts
myForEach([1,2,3], (a,i)=> {
console.log(i.toFixed());
'i' is possibly 'undefined'.18048'i' is possibly 'undefined'.
});
Try

In JavaScript, if you call a function with more arguments than there are parameters, the extra arguments are simply ignored.TypeScript behaves the same way.Functions with fewer parameters (of the same types) can always take the place of functions with more parameters.

Rule: When writing a function type for a callback,never write an optional parameter unless you intend tocall the function without passing that argument

Function Overloads

Some JavaScript functions can be called in a variety of argument counts and types.For example, you might write a function to produce aDate that takes either a timestamp (one argument) or a month/day/year specification (three arguments).

In TypeScript, we can specify a function that can be called in different ways by writingoverload signatures.To do this, write some number of function signatures (usually two or more), followed by the body of the function:

ts
functionmakeDate(timestamp:number):Date;
functionmakeDate(m:number,d:number,y:number):Date;
functionmakeDate(mOrTimestamp:number,d?:number,y?:number):Date {
if (d !==undefined &&y !==undefined) {
returnnewDate(y,mOrTimestamp,d);
}else {
returnnewDate(mOrTimestamp);
}
}
constd1 =makeDate(12345678);
constd2 =makeDate(5,5,5);
constd3 =makeDate(1,3);
No overload expects 2 arguments, but overloads do exist that expect either 1 or 3 arguments.2575No overload expects 2 arguments, but overloads do exist that expect either 1 or 3 arguments.
Try

In this example, we wrote two overloads: one accepting one argument, and another accepting three arguments.These first two signatures are called theoverload signatures.

Then, we wrote a function implementation with a compatible signature.Functions have animplementation signature, but this signature can’t be called directly.Even though we wrote a function with two optional parameters after the required one, it can’t be called with two parameters!

Overload Signatures and the Implementation Signature

This is a common source of confusion.Often people will write code like this and not understand why there is an error:

ts
functionfn(x:string):void;
functionfn() {
// ...
}
// Expected to be able to call with zero arguments
fn();
Expected 1 arguments, but got 0.2554Expected 1 arguments, but got 0.
Try

Again, the signature used to write the function body can’t be “seen” from the outside.

The signature of theimplementation is not visible from the outside.When writing an overloaded function, you should always havetwo or more signatures above the implementation of the function.

The implementation signature must also becompatible with the overload signatures.For example, these functions have errors because the implementation signature doesn’t match the overloads in a correct way:

ts
functionfn(x:boolean):void;
// Argument type isn't right
functionfn(x:string):void;
This overload signature is not compatible with its implementation signature.2394This overload signature is not compatible with its implementation signature.
functionfn(x:boolean) {}
Try
ts
functionfn(x:string):string;
// Return type isn't right
functionfn(x:number):boolean;
This overload signature is not compatible with its implementation signature.2394This overload signature is not compatible with its implementation signature.
functionfn(x:string |number) {
return"oops";
}
Try

Writing Good Overloads

Like generics, there are a few guidelines you should follow when using function overloads.Following these principles will make your function easier to call, easier to understand, and easier to implement.

Let’s consider a function that returns the length of a string or an array:

ts
functionlen(s:string):number;
functionlen(arr:any[]):number;
functionlen(x:any) {
returnx.length;
}
Try

This function is fine; we can invoke it with strings or arrays.However, we can’t invoke it with a value that might be a stringor an array, because TypeScript can only resolve a function call to a single overload:

ts
len("");// OK
len([0]);// OK
len(Math.random() >0.5 ?"hello" : [0]);
No overload matches this call. Overload 1 of 2, '(s: string): number', gave the following error. Argument of type 'number[] | "hello"' is not assignable to parameter of type 'string'. Type 'number[]' is not assignable to type 'string'. Overload 2 of 2, '(arr: any[]): number', gave the following error. Argument of type 'number[] | "hello"' is not assignable to parameter of type 'any[]'. Type 'string' is not assignable to type 'any[]'.2769No overload matches this call. Overload 1 of 2, '(s: string): number', gave the following error. Argument of type 'number[] | "hello"' is not assignable to parameter of type 'string'. Type 'number[]' is not assignable to type 'string'. Overload 2 of 2, '(arr: any[]): number', gave the following error. Argument of type 'number[] | "hello"' is not assignable to parameter of type 'any[]'. Type 'string' is not assignable to type 'any[]'.
Try

Because both overloads have the same argument count and same return type, we can instead write a non-overloaded version of the function:

ts
functionlen(x:any[] |string) {
returnx.length;
}
Try

This is much better!Callers can invoke this with either sort of value, and as an added bonus, we don’t have to figure out a correct implementation signature.

Always prefer parameters with union types instead of overloads when possible

Declaringthis in a Function

TypeScript will infer what thethis should be in a function via code flow analysis, for example in the following:

ts
constuser = {
id:123,
 
admin:false,
becomeAdmin:function () {
this.admin =true;
},
};
Try

TypeScript understands that the functionuser.becomeAdmin has a correspondingthis which is the outer objectuser.this,heh, can be enough for a lot of cases, but there are a lot of cases where you need more control over what objectthis represents. The JavaScript specification states that you cannot have a parameter calledthis, and so TypeScript uses that syntax space to let you declare the type forthis in the function body.

ts
interfaceDB {
filterUsers(filter: (this:User)=>boolean):User[];
}
 
constdb =getDB();
constadmins =db.filterUsers(function (this:User) {
returnthis.admin;
});
Try

This pattern is common with callback-style APIs, where another object typically controls when your function is called. Note that you need to usefunction and not arrow functions to get this behavior:

ts
interfaceDB {
filterUsers(filter: (this:User)=>boolean):User[];
}
 
constdb =getDB();
constadmins =db.filterUsers(()=>this.admin);
The containing arrow function captures the global value of 'this'.
Element implicitly has an 'any' type because type 'typeof globalThis' has no index signature.
7041
7017
The containing arrow function captures the global value of 'this'.
Element implicitly has an 'any' type because type 'typeof globalThis' has no index signature.
Try

Other Types to Know About

There are some additional types you’ll want to recognize that appear often when working with function types.Like all types, you can use them everywhere, but these are especially relevant in the context of functions.

void

void represents the return value of functions which don’t return a value.It’s the inferred type any time a function doesn’t have anyreturn statements, or doesn’t return any explicit value from those return statements:

ts
// The inferred return type is void
functionnoop() {
return;
}
Try

In JavaScript, a function that doesn’t return any value will implicitly return the valueundefined.However,void andundefined are not the same thing in TypeScript.There are further details at the end of this chapter.

void is not the same asundefined.

object

The special typeobject refers to any value that isn’t a primitive (string,number,bigint,boolean,symbol,null, orundefined).This is different from theempty object type{ }, and also different from the global typeObject.It’s very likely you will never useObject.

object is notObject.Always useobject!

Note that in JavaScript, function values are objects: They have properties, haveObject.prototype in their prototype chain, areinstanceof Object, you can callObject.keys on them, and so on.For this reason, function types are considered to beobjects in TypeScript.

unknown

Theunknown type representsany value.This is similar to theany type, but is safer because it’s not legal to do anything with anunknown value:

ts
functionf1(a:any) {
a.b();// OK
}
functionf2(a:unknown) {
a.b();
'a' is of type 'unknown'.18046'a' is of type 'unknown'.
}
Try

This is useful when describing function types because you can describe functions that accept any value without havingany values in your function body.

Conversely, you can describe a function that returns a value of unknown type:

ts
functionsafeParse(s:string):unknown {
returnJSON.parse(s);
}
 
// Need to be careful with 'obj'!
constobj =safeParse(someRandomString);
Try

never

Some functionsnever return a value:

ts
functionfail(msg:string):never {
thrownewError(msg);
}
Try

Thenever type represents values which arenever observed.In a return type, this means that the function throws an exception or terminates execution of the program.

never also appears when TypeScript determines there’s nothing left in a union.

ts
functionfn(x:string |number) {
if (typeofx ==="string") {
// do something
}elseif (typeofx ==="number") {
// do something else
}else {
x;// has type 'never'!
}
}
Try

Function

The global typeFunction describes properties likebind,call,apply, and others present on all function values in JavaScript.It also has the special property that values of typeFunction can always be called; these calls returnany:

ts
functiondoSomething(f:Function) {
returnf(1,2,3);
}
Try

This is anuntyped function call and is generally best avoided because of the unsafeany return type.

If you need to accept an arbitrary function but don’t intend to call it, the type() => void is generally safer.

Rest Parameters and Arguments

Background Reading:
Rest Parameters
Spread Syntax

Rest Parameters

In addition to using optional parameters or overloads to make functions that can accept a variety of fixed argument counts, we can also define functions that take anunbounded number of arguments usingrest parameters.

A rest parameter appears after all other parameters, and uses the... syntax:

ts
functionmultiply(n:number, ...m:number[]) {
returnm.map((x)=>n *x);
}
// 'a' gets value [10, 20, 30, 40]
consta =multiply(10,1,2,3,4);
Try

In TypeScript, the type annotation on these parameters is implicitlyany[] instead ofany, and any type annotation given must be of the formArray<T> orT[], or a tuple type (which we’ll learn about later).

Rest Arguments

Conversely, we canprovide a variable number of arguments from an iterable object (for example, an array) using the spread syntax.For example, thepush method of arrays takes any number of arguments:

ts
constarr1 = [1,2,3];
constarr2 = [4,5,6];
arr1.push(...arr2);
Try

Note that in general, TypeScript does not assume that arrays are immutable.This can lead to some surprising behavior:

ts
// Inferred type is number[] -- "an array with zero or more numbers",
// not specifically two numbers
constargs = [8,5];
constangle =Math.atan2(...args);
A spread argument must either have a tuple type or be passed to a rest parameter.2556A spread argument must either have a tuple type or be passed to a rest parameter.
Try

The best fix for this situation depends a bit on your code, but in general aconst context is the most straightforward solution:

ts
// Inferred as 2-length tuple
constargs = [8,5]asconst;
// OK
constangle =Math.atan2(...args);
Try

Using rest arguments may require turning ondownlevelIteration when targeting older runtimes.

Parameter Destructuring

Background Reading:
Destructuring Assignment

You can use parameter destructuring to conveniently unpack objects provided as an argument into one or more local variables in the function body.In JavaScript, it looks like this:

js
functionsum({a,b,c }) {
console.log(a +b +c);
}
sum({a:10,b:3,c:9 });

The type annotation for the object goes after the destructuring syntax:

ts
functionsum({a,b,c }: {a:number;b:number;c:number }) {
console.log(a +b +c);
}
Try

This can look a bit verbose, but you can use a named type here as well:

ts
// Same as prior example
typeABC = {a:number;b:number;c:number };
functionsum({a,b,c }:ABC) {
console.log(a +b +c);
}
Try

Assignability of Functions

Return typevoid

Thevoid return type for functions can produce some unusual, but expected behavior.

Contextual typing with a return type ofvoid doesnot force functions tonot return something. Another way to say this is a contextual function type with avoid return type (type voidFunc = () => void), when implemented, can returnany other value, but it will be ignored.

Thus, the following implementations of the type() => void are valid:

ts
typevoidFunc = ()=>void;
 
constf1:voidFunc = ()=> {
returntrue;
};
 
constf2:voidFunc = ()=>true;
 
constf3:voidFunc =function () {
returntrue;
};
Try

And when the return value of one of these functions is assigned to another variable, it will retain the type ofvoid:

ts
constv1 =f1();
 
constv2 =f2();
 
constv3 =f3();
Try

This behavior exists so that the following code is valid even thoughArray.prototype.push returns a number and theArray.prototype.forEach method expects a function with a return type ofvoid.

ts
constsrc = [1,2,3];
constdst = [0];
 
src.forEach((el)=>dst.push(el));
Try

There is one other special case to be aware of, when a literal function definition has avoid return type, that function mustnot return anything.

ts
functionf2():void {
// @ts-expect-error
returntrue;
}
 
constf3 =function ():void {
// @ts-expect-error
returntrue;
};
Try

For more onvoid please refer to these other documentation entries:

The TypeScript docs are an open source project. Help us improve these pagesby sending a Pull Request

Contributors to this page:
RCRyan Cavanaugh  (56)
OTOrta Therox  (15)
HAHossein Ahmadian-Yazdi  (4)
JWJoseph Wynn  (3)
SBSiarhei Bobryk  (2)
38+

Last updated: Apr 28, 2025