TypeScript

TypeScript Enums Made Simple

Picture of me, Omari
Omari Thompson-Edwards
Wed Feb 21 2024 4 min

Welcome to the world of TypeScript Enums. In this concise guide, we'll unravel the magic behind Enums, exploring how they can transform your TypeScript projects, making your code more readable and your development journey more enjoyable. Whether you're a seasoned developer or just starting, let's embark on a journey to demystify and harness the power of TypeScript Enums together."

What are enums?

Enums provide a structured way to define a set of named constants. They're particularly useful for scenarios where you need to represent a finite set of related values or options. You'll run into enums in a lot of programming languages, but here's what they look like in TypeScript. 

Numeric Enums.

Numeric enums are probably what you're used to if you're coming from another programming language. Very simply - each key represents a number.  TypeScript enums are auto-incrementing, so if you don't provide any values, the first key begins at 0, and increments by 1 each time.

We define our enum using the "enum" keyword, followed by the name of the enum, and the keys.

enum Direction {
    North,
    East,
    South,
    West,
}

console.log(Direction.North); //0
console.log(Direction.East); //1
console.log(Direction.South); //2
console.log(Direction.West); //3

If you provide a value to the first key in your enum it will act as an offset, letting you start off the incrementing from another value.

enum ClientErrorCode {
    BadRequest = 400,
    Unauthorized, //401
    PaymentRequired, //402
    Forbidden, //403
    NotFound, //404
}

Any keys without values will simply auto-increment from the value before:


enum ClientErrorCode {
    BadRequest = 400,
    Unauthorized, //401
    //Skipping PaymentRequired, 402,
    Forbidden = 403, //403
    NotFound, //404
}

String Enums.

The second type of enums are string enums. Like the name implies, they're identical to numeric enums, except the values are strings.

They can be handy for something like grouping together error messages:

enum ErrorMessage {
    UserNotFound = 'The user you are attempting to retrieve cannot be found.',
    UserPrivate = 'The user you are attempting to retrieve is private.',
    NotAdmin = 'You do not have the correct permissions to retrieve this user.',
}

They don't have auto-incrementing - you can imagine auto-incrementing a string wouldn't make much sense. The main benefit is that a string is a more readable value than a number, especially at runtime when the TypeScript aspect is gone.

Heterogeneous Enums.

TypeScript also lets you combine numeric and string enums into heterogenous enums:

enum Falsy {
    String = 'false',
    Number = 0,
}

 I've never found a valid use case for them, but they're one tool available at your disposal.

Computed vs Constant Members.

Enums can have computed and constant values. Enum values are considered constant if:

  • It's the first member in the enum and has no initialiser
  • It doesn't have an initialiser, and the preceding enum member was a numeric constant
  • The enum member is initialized with a constant enum expression. 
  • A constant enum expression is a subset of TypeScript expressions that can be fully evaluated at compile time. 
  • An expression is a constant enum expression if it is
    • A string or numeric literal
    • A reference to another constant enum member (even a different enum)
    • A parenthesized constant enum expression
    • one of the +, -, ~ unary operators applied to constant enum expression
    • +, -, *, /, %, <<, >>, >>>, &, |, ^ binary operators with constant enum expressions as operands

In all other cases, enum members are considered computed.

So this means all of these are constant:

enum ConstantMembers {
    First,
    Second,
    Third = 3,
    Fourth = Direction.North,
    Fifth = +1,
    Sixth = 2 + 2,
}

And all of these are computed:


enum ComputedMembers {
    First = getZero(),
    Second = foo.length,
    Third = "foo".charAt(0)
}

Why the hate for enums?

If you search for TypeScript enums, you'll see they get a lot of hate. Let's take a look at the reasons why.

Firstly, enums are the only part of TypeScript that is not a native JS feature. In the rest of TypeScript, you can simply strip away types and it will be JavaScript that's left, but that isn't true for enums.  As a result of this, enums result in some additional code generated at compile time:

enum Direction {
    Up,
    Right,
    Down,
    Left,
}

Here's the build output for that enum:

var Direction;
(function (Direction) {
    Direction[Direction["Up"] = 0] = "Up";
    Direction[Direction["Right"] = 1] = "Right";
    Direction[Direction["Down"] = 2] = "Down";
    Direction[Direction["Left"] = 3] = "Left";
})(Direction || (Direction = {}));

A little messy, we have an object contained inside an IIFE. Whether this additional code is worth it is up to you.

Another reason is that before TypeScript 5.0, enums were not as type-safe as the alternatives. Since TypeScript 5.0, enums are now union enums, which means TS will now create a unique type for each computed member, regardless of what type of enum it is.

Conclusion.

Thanks for reading! I hope this article was a useful, comprehensive guide to enums in TypeScript. If you liked this article, feel free to share it!

read more.

Me
Hey, I'm Omari 👋

I'm a full-stack developer from the UK. I'm currently looking for graduate and freelance software engineering roles, so if you liked this article, feel free to reach out.