TypeScript Basics: Generic Functions
Welcome to the Generic Functions lesson!
This lesson is shown as static text below. However, it's designed to be used interactively. Click the button below to start!
We've seen that arrays are generic data types: they can hold any type of data. What if we want to write a function that works on any type of data?
We can define generic functions using the same
<>syntax. When a function is generic, it has a "type hole", just likeArraydoes. The hole must be filled by a type, like hownumberfillsArray<number>.In the example below, we name the type hole
T. This is a common generic type parameter name, and it works well enough in simple situations.>
function first<T>(elements: Array<T>): T {return elements[0];}first<boolean>([true, false]);Result:
true
We fill the type hole when we call the function. For example,
first<boolean>fills the type hole withboolean. Let's trace thebooleantype's path through the function, imagining what the compiler does. At each step, it replaces theTtype parameter with the actual type.(The next few examples are pseudocode, meant to show how the
booleantype fills the type holes. Unlike most code in this course, we won't actually run these examples.)First, the
booleanin our function call fills the type holeTin thefirst<T>function definition.>
function first<boolean>(elements: Array<T>): T {return elements[0];}first<boolean>([true, false]);Second, the argument
(elements: Array<T>)becomes(elements: Array<boolean>).>
function first<boolean>(elements: Array<boolean>): T {return elements[0];}first<boolean>([true, false]);The array that we pass to
first<boolean>must match the definition, which is nowArray<boolean>. Fortunately, the array that we're passing,[true, false], does match that type!Third, the return type of
firstisT, which is stillboolean, sofirst<boolean>returns aboolean.>
function first<boolean>(elements: Array<boolean>): boolean {return elements[0];}first<boolean>([true, false]);Fourth and finally, all of the types match, so the function type checks. Just for completeness, we'll delete the
Ttype parameter from our pseudocode, both in the definition offirstand in our function call,first([true, false]). That leaves us with a new version offirstthat only works on booleans.>
function first(elements: Array<boolean>): boolean {return elements[0];}first([true, false]);Tracing generic types like this will feel awkward at first. But so did tracing a series of function calls when you first learned to do it! Like anything in programming, it gets easier with practice. And continuing through this course will give you a lot of that practice!
When we call a generic function, we fill the type hole using
<>. These types must match! For example, if we callfirst<string>, then we have to pass an array of strings.>
function first<T>(elements: Array<T>): T {return elements[0];}first<string>(['a', 'b']);Result:
'a'
>
function first<T>(elements: Array<T>): T {return elements[0];}first<string>([1, 2]);Result:
type error: Type 'number' is not assignable to type 'string'.
>
function first<T>(elements: Array<T>): T {return elements[0];}first<string>('a');Result:
type error: Argument of type 'string' is not assignable to parameter of type 'string[]'.
The type parameters can vary every time we use the function:
first<string>,first<number>, etc. This is where generics get their name. Generic means "relating to a class or group of things; not specific." Ourfirstfunction works with any array contents. It's not specific; it's generic.Generics allow us to write one function that can be reused with many types. Dynamic languages like JavaScript can also do this. However, TypeScript generics let us do it while maintaining our guarantee that the types all match.
>
function last<T>(elements: Array<T>): T {return elements[elements.length - 1];}let results: [string, number] = [last<string>(['a', 'b', 'c']),last<number>([1, 2, 3]),];results;Result:
['c', 3]
Generics also work when we assign the result to an inferred variable.
>
function first<T>(elements: Array<T>): T {return elements[0];}let n = first<number>([1, 2]);let n2 = n;n2;Result:
1
In that example,
nandn2both have the inferred typenumber. We can trace the compiler's logic again:- The compiler sees us calling
first<T>asfirst<number>. - So
Tisnumber. - So
first<T>(elements: Array<T>): Treturns anumber. - That return value is assigned to
n, sonmust be a number. - Then
nis assigned ton2, son2must be a number.
- The compiler sees us calling
When you compile your source code, the compiler performs thousands of these small type checking steps. At the end, all of the types must match. If anything doesn't match, that's a type error.
>
function second<T>(elements: Array<T>): T {return elements[1];}let n = second<number>([1, 2]);let n2: string = n;n2;Result:
type error: Type 'number' is not assignable to type 'string'.
We named our type variable
T, and you'll see a lot of real-world code and examples using that name. It just means "this is the function's type parameter". That's fine for small functions likefirstandsecond, but more complex functions deserve specific type names. For example,Array<Element>is more specific thanArray<T>: it tells us that the type parameter is used as an array element type.>
function first<Element>(elements: Array<Element>): Element {return elements[0];}first<boolean>([true, false]);Result:
true
Our generic examples have all involved arrays so far, because we haven't seen enough TypeScript features to build other kinds of generic examples. However, there's nothing special about arrays!
For example, we can write a function that says "I take any value, and return an array containing that value." In that case, there's still an array involved in the return value. But the function's argument doesn't involve any arrays at all.
>
function wrapInArray<T>(value: T): Array<T> {return [value];}- Note: this code example reuses elements (variables, etc.) defined in earlier examples.
>
wrapInArray(1);Result:
[1]
- Note: this code example reuses elements (variables, etc.) defined in earlier examples.
>
wrapInArray('a');Result:
['a']
As we see more TypeScript features, we'll also see how they interact with generic types.