A practical use for bitmasks in Javascript
Bitmasks are great at combining multiple true/false flags into one value.
In computer science, a mask or bitmask is data that is used for bitwise operations, particularly in a bit field. Using a mask, multiple bits in a byte can be set either on, off or inverted from on to off in a single bitwise operation.
In this article, I'm going to focus on how I use bitmasks to reduce the size of entries in a database.
Bitmasks are managed using bitwise operators which are rare and may confuse less experienced developers. ESLint even has a default setting to stop you from using them. The rationale used is bitwise operators look more like typos than actual features.
However, If used correctly, they can have a huge impact on the size, speed, and scalability of your database, which can save you a lot of money in the long run.
I'm going to show a common way I use bitmasks to store and manage a user's status.
Model
Each value (except 0) in Status is an incrementing power of 2. The power of 2 is significant because each value represents a bit in a binary number.
You can visualize this by laying the values of Status horizontally with the highest value at the start.
TFA_ENABLED | PAYMENT_VERIFIED | EMAIL_VERIFIED | ADMIN | ACTIVE
-----------------------------------------------------------------
0 | 0 | 0 | 0 | 0
As a user's Status changes, turn each corresponding bit on or off and store the integer value.
Example: An Active user with only two-factor authentication enabled
Bianry Visual Layout
TFA_ENABLED | PAYMENT_VERIFIED | EMAIL_VERIFIED | ADMIN | ACTIVE
-----------------------------------------------------------------
1 | 0 | 0 | 0 | 1
Users Table
id status
----------------|-----------
1 17 -> 10001
We store the status as an integer in the database but treat it as a binary number in our application.
You can store up to 32 true/false flags in a bitmask. If you need to add another status value, just add it to the end of the enum. You won't have to mess around with adding another column to your database.
Next, I'll go through some examples on how to manage a bitmask value.
Create User
When a user is first created, only the ACTIVE status needs to be set.
const user: IUser = {
status: Status.ACTIVE
...other user stuff
}
db.set(user)
Stored Value
Bianry Visual Layout
TFA_ENABLED | PAYMENT_VERIFIED | EMAIL_VERIFIED | ADMIN | ACTIVE
-----------------------------------------------------------------
0 | 0 | 0 | 0 | 1
Users Table
id status
----------------|-----------
1 1 -> 00001
Turn Payment Flag On
A user has provided their payment information. To flip a bit to its on state, use the |=
operator.
db.update({ status: (user.status |= Status.PAYMENT_VERIFIED) })
Stored Value
Bianry Visual Layout
TFA_ENABLED | PAYMENT_VERIFIED | EMAIL_VERIFIED | ADMIN | ACTIVE
-----------------------------------------------------------------
0 | 1 | 0 | 0 | 1
Users Table
id status
----------------|-----------
1 9 -> 01001
Turn Email Verified Flag On
A user has verified their email. Use the same |=
operator as before, just change the Status value.
db.update({ status: (user.status |= Status.EMAIL_VERIFIED) })
Stored Value
Bianry Visual Layout
TFA_ENABLED | PAYMENT_VERIFIED | EMAIL_VERIFIED | ADMIN | ACTIVE
-----------------------------------------------------------------
0 | 1 | 1 | 0 | 1
Users Table
id status
----------------|-----------
1 13 -> 01101
Turn Flag Off
A user's payment method has been removed. To flip a bit back to its off state, use the &= ~
operator.
db.update({ status: (user.status &= ~Status.PAYMENT_VERIFIED) })
Stored Value
Bianry Visual Layout
TFA_ENABLED | PAYMENT_VERIFIED | EMAIL_VERIFIED | ADMIN | ACTIVE
-----------------------------------------------------------------
0 | 0 | 1 | 0 | 1
Users Table
id status
----------------|-----------
1 5 -> 00101
Toggle Flag On/Off
Alternatively, you can toggle a bit on and off with the ^=
operator
db.update({ status: (user.status ^= Status.PAYMENT_VERIFIED) })
Check Flag
A user has made a request for a paid-only feature. To check that they have the PAYMENT_VERIFIED flag set on their status, use the &
operator.
// helper function to check any flag
const isFlagSet = (actual: number, expected: number): boolean = {
const flag = actual & expected;
return flag === expected;
};
const user = db.users.find(id)
const hasPayment = isFlagSet(user.status, Status.PAYMENT_VERIFIED)
I hope these examples help anyone interested in using bitmasks in their own application. They can take a bit of time to get used to, but the benefits are clear.