Occasionally you may find yourself writing a function that could take a number of different types. Typescript allows you to create a generic type parameter for this purpose.
A generic type parameter is a placeholder that we can use to enforce a type-level constraint in multiple places. It's easiest if we walk through an example.
Let's look at the “Hello world” of generics — the identity function. The identity function simply returns what was passed in. In plain JavaScript it would look like this:
function identity(arg) {
return arg
}
identity(2) // returns 2
identity("Hello") // returns "Hello"
Writing this in Typescript we might be tempted to use theany
type to cover all the specific cases we could pass in:
function identity(arg: any): any {
return arg
}
Although this would work, we're losing a lot of information about the return type of the function, and so we're not gaining anything by using TypeScript here.
We could overload the function to give it multiple types:
type Identity = [
(arg: string): string
(arg: number): number
]
function identity: Identity (arg) {
return arg
}
}
Notice in this example I've separated the type signature to keep things a little tidier. This method could work, however it's overly verbose and will get messy as you try to include every type that is required.
Fortunately TypeScript allows you to define “Generic type parameters”. We can rewrite our example to make use of this:
function identity<T>(arg: T): T {
return arg
}
Note the funny angle brackets here (<T>
). This is how we define a type variableT
¹. TypeScript will store the type passed to identity
in theT
variable which allows us to reference it in our type signature.
We can now call this in two ways.
let result = identity<string>(“Hello”) // typeof result === 'string'
let result = identity(42) // typeof result === 'number'
Our solution is now pretty generic as it can work with a range of types, however it's more precise than usingany
as we don't lose any information.
A side note — when using a type alias with generics like this:
type Identity<T> = {
(arg: T): T
}
function identity: Identity (arg) {
return arg
}
}
We have to define our type when we call the function otherwise we get an error:
let valid = identity<string>("Hello")
// valid === "Hello"
let invalid = identity("Hello")
// Error TS2314: Generic type 'Identity' requires 1 type argument(s)
Let's say we want to make our identity function log out the length of the argument as well. We may be tempted to write this:
function identity<T>(arg: T): T {
console.log(arg.length)
return arg
}
However, our editor should highlight an error here:
Property 'length' does not exist on type 'T'.
This makes sense as we haven't defined Type to have a length property. If we're only interested in this function being used with arrays we could change our InlineCode to the following and get rid of the error:
function identity<T>(arg: Array<T>): Array<T> {
console.log(arg.length)
return arg
}
This will now work for arrays of any type. If we want it to work with simple strings as well we can create an interface:
interface HasLength {
length: number
}
function identity<T extends HasLength>(arg: T): T {
console.log(arg.length)
return arg
}
Now we can call this with an array or a string and it will work as expected, but if we try to call it with a number we will get the following error message:
Argument of type 'number' is not assignable to parameter of type 'HasLength'.
T
), but it's common to use single uppercase characters.Self taught software developer with 11 years experience excelling at JavaScript/Typescript, React, Node and AWS.
I love learning and teaching and have mentored several junior developers over my career. I find teaching is one of the best ways to solidify your own learning, so in the past few years I've been maintaining a technical blog where I write about some things that I've been learning.
I'm passionate about building a teams culture and processes to make it efficient and satisfying to work in. In many roles I have improved the quality and reliability of the code base by introducing or improving the continuous integration pipeline to include quality gates.