Type Inference in TypeScript: Understanding Contextual Typing
In this video, you will learn how Contextual Typing works in TypeScript.
Code
let amount = 99.9let 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.