Enumeration in TypeScript

Greg Pabian
3 min readApr 26, 2020

--

Using TypeScript, comparably to other programming languages, we can define finite numerical (and string-based) sets by using a data structure called enumeration. As I am going to prove later in this article, enumerations fit well together with other TypeScript constructs like namespaces and literals.

Enumeration with numbers

We can create an enumeration type indexed with a number value:

const enum ENumbers {
ONE = 1,
TWO = 2,
THREE = 3,
// ...
}

I applied the const modifier to the aforementioned snippet to declare ENumbers — this ensures that the structure leaves no additional JavaScript code during runtime (if for some reasons we want the TypeScript compiler to emit an object with the defined properties, we can leave it without const). Interestingly enough, we are allowed to merge multiple const enum statements across our codebase but obviously cannot merge const enum and regular enum statements for the same type — this stems from the fact that at its core, enum declarations behave in a similar fashion to interface declarations.

We can declare a similar enumeration concept using number literals:

type TNumbers = 1 | 2 | 3; // etc.

Using number literals makes such definition more terse at the expense of losing all the previously defined labels. For what is worth, types never leave any runtime trace as well.

Enumeration with strings

Similarly to numbers, we can define enumeration types using strings, like in the following snippet:

const enum ELetters {
a = 'a',
b = 'b',
c = 'c',
// ...
}

As you might realise, the definition seems to contain complexity which is not required (the label and the value is the same). Another question is whether we should use upper case letters for labels or not.

ELetters can be reduced to a string literal with ease:

type TLetters = 'a' | 'b' | 'c';

Interoperability of enumerations and namespaces

We can use the name of the enumeration type to define an namespace. We can use such a namespace to e.g. allow a function to belong to a certain type:

enum ECountry {
US = 'US',
UK = 'UK',
DE = 'DE',
FR = 'FR',
}
namespace ECountry {
export const getPrice = (country: ECountry): number => {
switch(country) {
case ECountry.US:
case ECountry.UK:
return 10;
default:
return 5;
}
}
}

In the aforedefined example, we defined a function called getPrice that can be called in a special fashion: ECountry.getPrice(...) . Naturally, we can leverage this pattern in multitude of different ways, defining other structures — please note though that overusing it might result in a rather unmaintainable codebase.

Interoperability of enumerations and literals

As our enumeration types might hold a lot of additional code which is not related to the very enumeration aspect of them, we might want to abstract them away (or when we want to distribute a codebase as a library and we don’t want to expose enumeration types). This is possible by defining a type literal which values correspond to the values of the enumeration.

Let’s define a type literal for the ECountry enumeration and let’s define an interface that returns pricing methods:

type TCountry = 'US' | 'UK' | 'DE' | 'FR';interface IPricing {
getPrice: (country: TCountry) => number;
}

Please note that the IPricing.getPrice function requires a TCountry type, not ECountry. Thanks to the structural typing properties of the TypeScript language, we can substitute TCountry with ECountry nevertheless:

const getPricing = (): IPricing => ({
getPrice: ECountry.getPrice,
});

This discovery allows us to treat enums as potential vectors for different business logics. One obvious application could be having different enums for production and for testing code. Whether this pattern is viable for a business project or not, I will leave that to the reader to decide.

Summary

As we might have seen, TypeScript allows us to convey enumeration using either enumeration types or literal types and it is possible to use both approached interchangeably. Personably, I prefer using literal types as the structure is way shorter and I get the same type support from the TypeScript compiler as compared to using enumeration types.

--

--

Greg Pabian
Greg Pabian

Written by Greg Pabian

A Full-stack Software Engineer that loves building products. Disclaimer: https://gist.github.com/grzpab/3cf57878ffce7d5271298ccc473bcb98

No responses yet