Typescript — Generics, and overloads 👨‍🔬


This article is part of the collection “TypeScript Essentials”, this is Chapter four.

In the last article “TypeScript — Learn the basics”, we saw all basic usages of TypeScript types.

By the end of this article, you’ll understand why generic types are essential for real-world usage.

Generic — Configurable types

While basic types like interfaces are useful to describe data and basic functions signatures, generics helps making types “open” and reusable.

Why use Generic types?

Imagine we want to expose the following helper in our application:

function withUID (obj) { 
return Object.assign({}, obj, { uuid: _.uniqueId() });

The more straightforward way to “type” this function would be:

function withUID (obj: any) { 
return Object.assign({}, obj, { uuid: _.uniqueId() });

We use any because we want to accept any value — indeed we can use object.

The problem is that the inferred return type of the function is any .

By using scalar types (object, …) or any,
we prevent TypeScript to infer the return type.

To overcome this problem, we’re gonna use generics.

Using Generic types

In languages like C# and Java, one of the main tools in the toolbox for creating reusable components is generics, that is, being able to create a component that can work over a variety of types rather than a single one.


As explained this excerpt of the TypeScript documentation, we want to create functions that accept many kinds of types, without losing type — without using any.

Let’s see the withUID using generics:

function withUID<T>(obj: T) { 
return Object.assign({}, obj, { uuid: _.uniqueId() });

The <> syntax is reserved for describing generic type.
A generic expose many “type arguments”, listed between the <>.

Generics can be applied to interfaces , class and function.

interface A<T, S> {
a: T;
b: S;
c: { id: string } & S;

As you can see, we can define as many “type argument” as needed.

Here, the T type is inferred from the passed argument type.

If no type argument type is explicitly passed, TypeScript will try to infer them by the values passed to the function arguments.

Example, for withUID, T is inferred from the type of obj argument.

Generics can “extends”

The type argument can provide some constraints by using the extends keyword.

function withUID<T extends object>(obj: T) { 
return Object.assign({}, obj, { uuid: _.uniqueId() });
withUID({ a: 1 }); // is valid
withUID("hello"); // is NOT valid

Here, T should fulfill the condition “is an object type”.

interface Person { name: string; }function withUID<T extends Person>(obj: T) { 
return Object.assign({}, obj, { uuid: _.uniqueId() });
withUID({ name: "POLY", surname: "Chack" }); // is valid

Generics can have a “default type value”

Finally, argument types can also have “default” or “computed value”.

interface A<T=string> {
name: T;
const a:A = { name: "Charly" };
const a:A<number> = { name: 101 };

This is particularly important for interfaces which unlike function generics cannot omit type argument, example:

For interfaces, TypeScript cannot infer type arguments based on properties value, unlike for functions

That’s why “default type value” is a “nice to know”:

This is correct.

Tips — default type arguments can reuse other type arguments

The example below describe how we can define a type of argument on-top of another:

function MyFunction<T extends Person, S=T&{ ssid: string }>(
person: S
): S {
/* ... */

Overloads — Extendable Function types

Generics are convenient for the functions that are responsible for simple isolated computation or predictable return value for given a input.

Unfortunately, everything isn’t this simple, some “big” functions may have high complexity with the variant return type.
Generics can be used in combination with “overloads” to overcome this issue.

For example, how can we type this function?

function getArray(...args) {
if (args.length === 1 && typeof args[0] === 'number') {
return new Array(args[0])
} else if (args.length > 1) {
return Array.from(args);
getArray(5) // => [undefined x 5]getArray('a', 'b', 'c') // => ['a', 'b', 'c']

Here is the inferred result type:

The solution is to use “Overloads”.

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.


Overloads are the fact of listing all the possible input/output types couple for a given function.


ℹ️ Please note that :

  • overloads have nobody
  • the implementation function must have as open as possible typing
    (to allow overloading)

Real-world examples

interface with Generic example: React.Component

You probably know how to declare a React Component in ES6:

class MyComponent extends React.Component {}

With TypeScript, the React.Component class refer to Component type.

P stands for Props type and S for State type

Component type extends ComponentLifecycle — which provide all famous lifecycle methods:

You can see here how React use Generics to propagate Props and State types to class methods

So, if you use React with TypeScript, remember to provide type arguments for Props and State!

interface Props { user: User }
interface State {}
class MyComponent extends React.Component<Props, State> {
state: State = {}; // important!

// ...

Overloads example: lodash _.filter()

_.filter can take different types of arguments as value, which results in a pretty complex mix of Generic with Overloads typing.

All lodash functions provide this advanced and complete typing!

I hope you now understand the concepts of Generic and Overloads and especially why it’s important is a real-world TypeScript project.

If you want to dive further and level-up your typing skills, I advise you to take a look at the following typings :

The next chapter —“Typescript — Super-types 💪”— will cover more complex cases with more real-world examples 👷

Latest Serie is out!

Interested in React? You should check-out my last publication:




Helping you to get the most of new web technologies ✍️🎙 | https://noti.st/charlypoly

Love podcasts or audiobooks? Learn on the go with our new app.

Recommended from Medium

React Native Clean Architecture — ResoCoder’s way

Introduction to React Hooks

Step-by-Step Guide To Set up Your Ubuntu VPS with NodeJS and PostgreSQL

Depiction of the Nginx — NodeJS — PostgreSQL Setup

The Best Way To Build React Applications

From @Balthomeservice on Twitter

Screeps: Understanding the game

A Complete Introduction to Node Buffers

How to update extension via composer

Charly Poly

Charly Poly

Helping you to get the most of new web technologies ✍️🎙 | https://noti.st/charlypoly

More from Medium

Setting up fool-proof linter rules

TypeScript Enums: What they are and why you should avoid them

RxJS interop incompatibility: A tale as old as time

TypeScript: Boost Your Code Quality