Was this page helpful?

This page has been deprecated

This handbook page has been replaced,go to the new page

Functions

Functions are the fundamental building block of any application in JavaScript.They’re how you build up layers of abstraction, mimicking classes, information hiding, and modules.In TypeScript, while there are classes, namespaces, and modules, functions still play the key role in describing how todo things.TypeScript also adds some new capabilities to the standard JavaScript functions to make them easier to work with.

Functions

To begin, just as in JavaScript, TypeScript functions can be created both as a named function or as an anonymous function.This allows you to choose the most appropriate approach for your application, whether you’re building a list of functions in an API or a one-off function to hand off to another function.

To quickly recap what these two approaches look like in #"#function-types" aria-label="function types permalink">Function Types

Typing the function

Let’s add types to our simple examples from earlier:

ts
functionadd(x:number,y:number):number {
returnx +y;
}
 
letmyAdd =function (x:number,y:number):number {
returnx +y;
};
Try

We can add types to each of the parameters and then to the function itself to add a return type.TypeScript can figure the return type out by looking at the return statements, so we can also optionally leave this off in many cases.

Writing the function type

Now that we’ve typed the function, let’s write the full type of the function out by looking at each piece of the function type.

ts
letmyAdd: (x:number,y:number)=>number =function (
x:number,
y:number
):number {
returnx +y;
};
Try

A function’s type has the same two parts: the type of the arguments and the return type.When writing out the whole function type, both parts are required.We write out the parameter types just like a parameter list, giving each parameter a name and a type.This name is just to help with readability.We could have instead written:

ts
letmyAdd: (baseValue:number,increment:number)=>number =function (
x:number,
y:number
):number {
returnx +y;
};
Try

As long as the parameter types line up, it’s considered a valid type for the function, regardless of the names you give the parameters in the function type.

The second part is the return type.We make it clear which is the return type by using an arrow (=>) between the parameters and the return type.As mentioned before, this is a required part of the function type, so if the function doesn’t return a value, you would usevoid instead of leaving it off.

Of note, only the parameters and the return type make up the function type.Captured variables are not reflected in the type.In effect, captured variables are part of the “hidden state” of any function and do not make up its API.

Inferring the types

In playing with the example, you may notice that the TypeScript compiler can figure out the type even if you only have types on one side of the equation:

ts
// The parameters 'x' and 'y' have the type number
letmyAdd =function (x:number,y:number):number {
returnx +y;
};
 
// myAdd has the full function type
letmyAdd2: (baseValue:number,increment:number)=>number =function (x,y) {
returnx +y;
};
Try

This is called “contextual typing”, a form of type inference.This helps cut down on the amount of effort to keep your program typed.

Optional and Default Parameters

In TypeScript, every parameter is assumed to be required by the function.This doesn’t mean that it can’t be givennull orundefined, but rather, when the function is called, the compiler will check that the user has provided a value for each parameter.The compiler also assumes that these parameters are the only parameters that will be passed to the function.In short, the number of arguments given to a function has to match the number of parameters the function expects.

ts
functionbuildName(firstName:string,lastName:string) {
returnfirstName +" " +lastName;
}
 
letresult1 =buildName("Bob");// error, too few parameters
Expected 2 arguments, but got 1.2554Expected 2 arguments, but got 1.
letresult2 =buildName("Bob","Adams","Sr.");// error, too many parameters
Expected 2 arguments, but got 3.2554Expected 2 arguments, but got 3.
letresult3 =buildName("Bob","Adams");// ah, just right
Try

In JavaScript, every parameter is optional, and users may leave them off as they see fit.When they do, their value isundefined.We can get this functionality in TypeScript by adding a? to the end of parameters we want to be optional.For example, let’s say we want the last name parameter from above to be optional:

ts
functionbuildName(firstName:string,lastName?:string) {
if (lastName)returnfirstName +" " +lastName;
elsereturnfirstName;
}
 
letresult1 =buildName("Bob");// works correctly now
letresult2 =buildName("Bob","Adams","Sr.");// error, too many parameters
Expected 1-2 arguments, but got 3.2554Expected 1-2 arguments, but got 3.
letresult3 =buildName("Bob","Adams");// ah, just right
Try

Any optional parameters must follow required parameters.Had we wanted to make the first name optional, rather than the last name, we would need to change the order of parameters in the function, putting the first name last in the list.

In TypeScript, we can also set a value that a parameter will be assigned if the user does not provide one, or if the user passesundefined in its place.These are called default-initialized parameters.Let’s take the previous example and default the last name to"Smith".

ts
functionbuildName(firstName:string,lastName ="Smith") {
returnfirstName +" " +lastName;
}
 
letresult1 =buildName("Bob");// works correctly now, returns "Bob Smith"
letresult2 =buildName("Bob",undefined);// still works, also returns "Bob Smith"
letresult3 =buildName("Bob","Adams","Sr.");// error, too many parameters
Expected 1-2 arguments, but got 3.2554Expected 1-2 arguments, but got 3.
letresult4 =buildName("Bob","Adams");// ah, just right
Try

Default-initialized parameters that come after all required parameters are treated as optional, and just like optional parameters, can be omitted when calling their respective function.This means optional parameters and trailing default parameters will share commonality in their types, so both

ts
functionbuildName(firstName:string,lastName?:string) {
// ...
}

and

ts
functionbuildName(firstName:string,lastName ="Smith") {
// ...
}

share the same type(firstName: string, lastName?: string) => string.The default value oflastName disappears in the type, only leaving behind the fact that the parameter is optional.

Unlike plain optional parameters, default-initialized parameters don’tneed to occur after required parameters.If a default-initialized parameter comes before a required parameter, users need to explicitly passundefined to get the default initialized value.For example, we could write our last example with only a default initializer onfirstName:

ts
functionbuildName(firstName ="Will",lastName:string) {
returnfirstName +" " +lastName;
}
 
letresult1 =buildName("Bob");// error, too few parameters
Expected 2 arguments, but got 1.2554Expected 2 arguments, but got 1.
letresult2 =buildName("Bob","Adams","Sr.");// error, too many parameters
Expected 2 arguments, but got 3.2554Expected 2 arguments, but got 3.
letresult3 =buildName("Bob","Adams");// okay and returns "Bob Adams"
letresult4 =buildName(undefined,"Adams");// okay and returns "Will Adams"
Try

Rest Parameters

Required, optional, and default parameters all have one thing in common: they talk about one parameter at a time.Sometimes, you want to work with multiple parameters as a group, or you may not know how many parameters a function will ultimately take.In JavaScript, you can work with the arguments directly using thearguments variable that is visible inside every function body.

In TypeScript, you can gather these arguments together into a variable:

ts
functionbuildName(firstName:string, ...restOfName:string[]) {
returnfirstName +" " +restOfName.join(" ");
}
 
// employeeName will be "Joseph Samuel Lucas MacKinzie"
letemployeeName =buildName("Joseph","Samuel","Lucas","MacKinzie");
Try

Rest parameters are treated as a boundless number of optional parameters.When passing arguments for a rest parameter, you can use as many as you want; you can even pass none.The compiler will build an array of the arguments passed in with the name given after the ellipsis (...), allowing you to use it in your function.

The ellipsis is also used in the type of the function with rest parameters:

ts
functionbuildName(firstName:string, ...restOfName:string[]) {
returnfirstName +" " +restOfName.join(" ");
}
 
letbuildNameFun: (fname:string, ...rest:string[])=>string =buildName;
Try

this

Learning how to usethis in JavaScript is something of a rite of passage.Since TypeScript is a superset of JavaScript, TypeScript developers also need to learn how to usethis and how to spot when it’s not being used correctly.Fortunately, TypeScript lets you catch incorrect uses ofthis with a couple of techniques.If you need to learn howthis works in JavaScript, though, first read Yehuda Katz’sUnderstanding JavaScript Function Invocation and “this”.Yehuda’s article explains the inner workings ofthis very well, so we’ll just cover the basics here.

this and arrow functions

In JavaScript,this is a variable that’s set when a function is called.This makes it a very powerful and flexible feature, but it comes at the cost of always having to know about the context that a function is executing in.This is notoriously confusing, especially when returning a function or passing a function as an argument.

Let’s look at an example:

ts
letdeck = {
suits: ["hearts","spades","clubs","diamonds"],
cards:Array(52),
createCardPicker:function () {
returnfunction () {
letpickedCard =Math.floor(Math.random() *52);
letpickedSuit =Math.floor(pickedCard /13);
 
return {suit:this.suits[pickedSuit],card:pickedCard %13 };
};
},
};
 
letcardPicker =deck.createCardPicker();
letpickedCard =cardPicker();
 
alert("card: " +pickedCard.card +" of " +pickedCard.suit);
Try

Notice thatcreateCardPicker is a function that itself returns a function.If we tried to run the example, we would get an error instead of the expected alert box.This is because thethis being used in the function created bycreateCardPicker will be set towindow instead of ourdeck object.That’s because we callcardPicker() on its own.A top-level non-method syntax call like this will usewindow forthis.(Note: under strict mode,this will beundefined rather thanwindow).

We can fix this by making sure the function is bound to the correctthis before we return the function to be used later.This way, regardless of how it’s later used, it will still be able to see the originaldeck object.To do this, we change the function expression to use the ECMAScript 6 arrow syntax.Arrow functions capture thethis where the function is created rather than where it is invoked:

ts
letdeck = {
suits: ["hearts","spades","clubs","diamonds"],
cards:Array(52),
createCardPicker:function () {
// NOTE: the line below is now an arrow function, allowing us to capture 'this' right here
return ()=> {
letpickedCard =Math.floor(Math.random() *52);
letpickedSuit =Math.floor(pickedCard /13);
 
return {suit:this.suits[pickedSuit],card:pickedCard %13 };
};
},
};
 
letcardPicker =deck.createCardPicker();
letpickedCard =cardPicker();
 
alert("card: " +pickedCard.card +" of " +pickedCard.suit);
Try

Even better, TypeScript will warn you when you make this mistake if you pass thenoImplicitThis flag to the compiler.It will point out thatthis inthis.suits[pickedSuit] is of typeany.

this parameters

Unfortunately, the type ofthis.suits[pickedSuit] is stillany.That’s becausethis comes from the function expression inside the object literal.To fix this, you can provide an explicitthis parameter.this parameters are fake parameters that come first in the parameter list of a function:

ts
functionf(this:void) {
// make sure `this` is unusable in this standalone function
}

Let’s add a couple of interfaces to our example above,Card andDeck, to make the types clearer and easier to reuse:

ts
interfaceCard {
suit:string;
card:number;
}
 
interfaceDeck {
suits:string[];
cards:number[];
createCardPicker(this:Deck): ()=>Card;
}
 
letdeck:Deck = {
suits: ["hearts","spades","clubs","diamonds"],
cards:Array(52),
// NOTE: The function now explicitly specifies that its callee must be of type Deck
createCardPicker:function (this:Deck) {
return ()=> {
letpickedCard =Math.floor(Math.random() *52);
letpickedSuit =Math.floor(pickedCard /13);
 
return {suit:this.suits[pickedSuit],card:pickedCard %13 };
};
},
};
 
letcardPicker =deck.createCardPicker();
letpickedCard =cardPicker();
 
alert("card: " +pickedCard.card +" of " +pickedCard.suit);
Try

Now TypeScript knows thatcreateCardPicker expects to be called on aDeck object.That means thatthis is of typeDeck now, notany, sonoImplicitThis will not cause any errors.

this parameters in callbacks

You can also run into errors withthis in callbacks, when you pass functions to a library that will later call them.Because the library that calls your callback will call it like a normal function,this will beundefined.With some work you can usethis parameters to prevent errors with callbacks too.First, the library author needs to annotate the callback type withthis:

ts
interfaceUIElement {
addClickListener(onclick: (this:void,e:Event)=>void):void;
}
Try

this: void means thataddClickListener expectsonclick to be a function that does not require athis type.Second, annotate your calling code withthis:

ts
classHandler {
info:string;
onClickBad(this:Handler,e:Event) {
// oops, used `this` here. using this callback would crash at runtime
this.info =e.message;
}
}
 
leth =newHandler();
uiElement.addClickListener(h.onClickBad);// error!
Argument of type '(this: Handler, e: Event) => void' is not assignable to parameter of type '(this: void, e: Event) => void'. The 'this' types of each signature are incompatible. Type 'void' is not assignable to type 'Handler'.2345Argument of type '(this: Handler, e: Event) => void' is not assignable to parameter of type '(this: void, e: Event) => void'. The 'this' types of each signature are incompatible. Type 'void' is not assignable to type 'Handler'.
Try

Withthis annotated, you make it explicit thatonClickBad must be called on an instance ofHandler.Then TypeScript will detect thataddClickListener requires a function that hasthis: void.To fix the error, change the type ofthis:

ts
classHandler {
info:string;
onClickGood(this:void,e:Event) {
// can't use `this` here because it's of type void!
console.log("clicked!");
}
}
 
leth =newHandler();
uiElement.addClickListener(h.onClickGood);
Try

BecauseonClickGood specifies itsthis type asvoid, it is legal to pass toaddClickListener.Of course, this also means that it can’t usethis.info.If you want both then you’ll have to use an arrow function:

ts
classHandler {
info:string;
onClickGood = (e:Event)=> {
this.info =e.message;
};
}
Try

This works because arrow functions use the outerthis, so you can always pass them to something that expectsthis: void.The downside is that one arrow function is created per object of type Handler.Methods, on the other hand, are only created once and attached to Handler’s prototype.They are shared between all objects of type Handler.

Overloads

JavaScript is inherently a very dynamic language.It’s not uncommon for a single JavaScript function to return different types of objects based on the shape of the arguments passed in.

ts
letsuits = ["hearts","spades","clubs","diamonds"];
 
functionpickCard(x:any):any {
// Check to see if we're working with an object/array
// if so, they gave us the deck and we'll pick the card
if (typeofx =="object") {
letpickedCard =Math.floor(Math.random() *x.length);
returnpickedCard;
}
// Otherwise just let them pick the card
elseif (typeofx =="number") {
letpickedSuit =Math.floor(x /13);
return {suit:suits[pickedSuit],card:x %13 };
}
}
 
letmyDeck = [
{suit:"diamonds",card:2 },
{suit:"spades",card:10 },
{suit:"hearts",card:4 },
];
 
letpickedCard1 =myDeck[pickCard(myDeck)];
alert("card: " +pickedCard1.card +" of " +pickedCard1.suit);
 
letpickedCard2 =pickCard(15);
alert("card: " +pickedCard2.card +" of " +pickedCard2.suit);
Try

Here, thepickCard function will return two different things based on what the user has passed in.If the users passes in an object that represents the deck, the function will pick the card.If the user picks the card, we tell them which card they’ve picked.But how do we describe this to the type system?

The answer is to supply multiple function types for the same function as a list of overloads.This list is what the compiler will use to resolve function calls.Let’s create a list of overloads that describe what ourpickCard accepts and what it returns.

ts
letsuits = ["hearts","spades","clubs","diamonds"];
 
functionpickCard(x: {suit:string;card:number }[]):number;
functionpickCard(x:number): {suit:string;card:number };
functionpickCard(x:any):any {
// Check to see if we're working with an object/array
// if so, they gave us the deck and we'll pick the card
if (typeofx =="object") {
letpickedCard =Math.floor(Math.random() *x.length);
returnpickedCard;
}
// Otherwise just let them pick the card
elseif (typeofx =="number") {
letpickedSuit =Math.floor(x /13);
return {suit:suits[pickedSuit],card:x %13 };
}
}
 
letmyDeck = [
{suit:"diamonds",card:2 },
{suit:"spades",card:10 },
{suit:"hearts",card:4 },
];
 
letpickedCard1 =myDeck[pickCard(myDeck)];
alert("card: " +pickedCard1.card +" of " +pickedCard1.suit);
 
letpickedCard2 =pickCard(15);
alert("card: " +pickedCard2.card +" of " +pickedCard2.suit);
Try

With this change, the overloads now give us type checked calls to thepickCard function.

In order for the compiler to pick the correct type check, it follows a similar process to the underlying JavaScript.It looks at the overload list and, proceeding with the first overload, attempts to call the function with the provided parameters.If it finds a match, it picks this overload as the correct overload.For this reason, it’s customary to order overloads from most specific to least specific.

Note that thefunction pickCard(x): any piece is not part of the overload list, so it only has two overloads: one that takes an object and one that takes a number.CallingpickCard with any other parameter types would cause an error.

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

Contributors to this page:
RCRyan Cavanaugh  (55)
DRDaniel Rosenwasser  (23)
OTOrta Therox  (18)
NSNathan Shively-Sanders  (4)
MFMartin Fischer  (1)
24+

Last updated: Dec 16, 2025