shaky.sh

Bit flags in TypeScript

I recently made a video about TypeScript Enums, and why you might not want to use them. But I want to go a little deeper on some seemingly strange enum behaviour.

You might not expect this to be valid TypeScript:

enum Role {
Executive = 0,
Manager = 1,
Engineer = 2
}

const myRole: Role = 3;

But it is. We can assign a value that isn't part of Role to a variable of type Role. Why would TypeScript allow this?

The reason seems to be supporting bit flags in TypeScript. The idea behind bit flags is that you can represent many boolean values in a single number, because that number can be represented in binary:

0 = 000
1 = 001
2 = 010
3 = 011
4 = 100
5 = 101
6 = 110
7 = 111

The first eight numbers (0 – 7) can all be represented as 3-digit binary numbers. In binary form, you can see how they can also represent all possible combinations of three boolean fields. The "ones column" represents the first boolean field, and so on.

If we want a TypeScript enum that represents a set of three boolean fields, we only need three members in the enum:

enum UserAttrs {
Verified = 1 << 0, // 1
Active = 1 << 1, // 2
Paying = 1 << 2, // 4
}

By convention, we're using the left-shift operator on 1 to set the values for these enum members. But we could have just as easily assigned the matching decimal numbers instead. When you left-shift 1, you're essentially choosing the column you want the binary 1 to be in. You can see how 1, 2, and 4 in the table above each have a single 1 in their respective columns.

Now, we can use this enum to create different combinations of attributes:

const verifiedAndActive = UserAttrs.Verified | UserAttrs.Active; // 3 = 011
const activeAndPaying = UserAttrs.Active | UserAttrs.Paying; // 6 = 110

We're combining these attributes with the OR operator, and this works much like a union in a TypeScript type: you get a value that can be either side of the OR operator (or both sides, in our case!). And these new values can be treated as being the type UserAttrs. As I understand it, this is the main reason you can assign any number to an enum type in TypeScript: it could be a combination of actual members of the enum. Notice that in our new values, a 1 in the "ones column" represents a user who has verified=true, a 1 in the "tens column" represents a user who has active=true, etc.

Now, you might wonder how we get the individual values out of our new combined values. Easy: use the AND operator:

function isVerified(attrs: UserAttrs): boolean {
return (attrs & UserAttrs.Verified) === UserAttrs.Verified;
}

isVerified(activeAndPaying); // false
isVerified(verifiedAndActive); // true

Again like in a TS type, the AND operator will give you anything that both sides share in common. Since UserAttrs.Verified only has a 1 in the "ones" column, the expression attrs & UserAttrs.Verified will return 1 (the value of UserAttrs.Verified) if attrs has a 1 in the same column. It doesn't matter what attrs has in the other columns.

As you probably know, bitwise operators aren't a TypeScript thing; this is one of the oldest computer science techniques for packing a bunch of data into less memory. Given that TypeScript is a pretty high-level language, we don't use this much. But it's cool that we have the option.

More Info