Type Inference in TypeScript: Understanding Contextual Typing

In this video, you will learn how Contextual Typing works in TypeScript.

Code

let amount = 99.9
let isDebit = true;
function multiply(a: number, b = 10) {
return a * b;
}
type Transaction = { amount: number, isDebit: boolean };
type TotalExpenses = (...transactions: Transaction[]) => number;
const totalExpenses: TotalExpenses = (...ts) => {
const debitTransactions = ts.filter(_ => _.isDebit);
return debitTransactions.reduce((sum, t) => sum + t.amount, 0)
}
const t1: Transaction = { amount: 10, isDebit: true };
const t2: Transaction = { amount: 20, isDebit: true };
const total = totalExpenses(t1, t2);
console.log(total)

The interactive code is available at TypeScript Playground

Full Transcript

Hello Friends, It's Harit from BonsaiiLabs. Today, I will explain how Contextual Typing works in TypeScript.

To start, I will create 2 variables called amount, and isDebit with values. Now, as I hover on the amount, we can see that TypeScript inferred its type as a number. For isDebit, the type inferred is boolean.

Now, I will create a new function called multiply. It has 2 parameters - a which I said is of type number, and b, for which I used parameter default, and did not specify the type of the variable. In the function body, I return the multiplication of a and b. Now, as I hover on multiply, we can see that TypeScript inferred the type of b as a number. It did so by looking at the value on its right-hand side which is 10, and therefore concluded that b must be of type number. It also inferred the return type of function as the number as multiplying two numbers yields a number.

So, in these examples, TypeScript inferred the type of the variable by looking at the values on the right-hand side of the expression.

Now, in the case of Contextual typing, the type inference works by looking at the left-hand side and filling the types on the right hand-side. Let me give you an example.

I will first create a type called Transaction with 2 members called amount of type number, and isDebit of type boolean. Next, I will create another type called TotalExpenses which represents a function signature. It takes a rest parameter called transactions which is of type Transaction[] and returns a number. Now, since this is a type declaration only, we need to implement a function which is of type TotalExpenses.

For that, I will create a constant called totalExpenses() of type TotalExpenses. Now, after the equals sign, as I open parentheses, we see that TypeScript presents a pop-up and guides us to fill the details according to what it expects. It expects one or more transactions and wants an implementation that returns a number. Now, this is contextual typing, as the left side of the expression is guiding the types on the right-hand side. This is the opposite of how types were inferred in the previous examples.

I will add the parameter as ts and open the curly braces for implementation. Right now, TypeScript shows an error. As I hover, it tells us that this method should return a number, and currently, it is returning void. In JavaScript, if you do not return a value from the function, it returns void, which is the same behavior in TypeScript and that's how it compares the void with the expected type number and throwing an error. I will create a constant called debitTransactions which will contain all the transactions where isDebit is true. Now see as I hit dot after ts, I get all the methods that exist on an array. This is fantastic, I did not explicitly say that ts is of type Transaction[], but because of contextual typing TypeScript knows that ts is an array and provides us with all the methods that exist on an array. I will call filter and only take out the debit transactions. Next, I will run reduce method on debitTransactions and add the amount field. I get an error since I did not provide the initial value of the accumulator, it picked the first transaction as the initial value which is a Transaction, and therefore says that we cannot add a Transaction and a number. I will add 0 as the initial value, and the code compiles successfully. So, you may now appreciate how contextual typing is a useful type inference technique and guides developers in writing correct implementation without adding the types over and over again.

Let's test our implementation next! I will add 2 debit transactions with different amounts. I will then call totalExpenses() with t1 and t2 and save the result in a variable called total. I will finally print the value on the console and execute this code. We can confirm that this code works correctly.

Great, so I hope you now understand what Contextual Typing is and how it works in TypeScript. Thank you for your time, and I will see you again in another video.