DEV Community

Ember
Ember

Posted on

Beyond Enums and Arrays: Why Bitwise Flags Are Your Next TypeScript Tool

Hi there! Have you ever built a service with a lot of conditional flags? For example, can user read, create or delete his or someone else's content; kick, ban, mute, invite others; manage all groups or only some of them; or perhaps feature toggle like isDarkModeEnabled, isNotificationAllowed and etc. If so, you've probably faced the challenge of managing these flags efficiently. You might have tried different approaches:

Dummy approaches

Array of strings

Alright, you've decided to store enabled features as string in an array:

const userPermissions = ["read", "write", "delete"];
Enter fullscreen mode Exit fullscreen mode

On the face of it, there's nothing bad. Until you need to check permissions:

// Checking if user can write
if (userPermissions.includes("write")) {
    // Do something
}

// Checking multiple permissions
if (userPermissions.includes("read") && userPermissions.includes("write")) {
    // Do something
}
Enter fullscreen mode Exit fullscreen mode

What's problem here?

  1. Performance issues: Array lookups are O(n) - slow for large sets.
  2. No type safety - it's too easy to make a typo and TypeScript won't help you!
  3. Memory inefficient: Storing multiple strings for what could be bits

Not that bad

Object/enum of booleans

This variant is not that bad how you would think, really. If your app is small, you don't need to transfer the data, store it in an external storage (like database) or have multiple state, this is ideal for you.

const userPermissions = {
    canRead: true,
    canWrite: false, 
    canDelete: true
};

const featureFlags = {
    isDarkModeEnabled: true,
    isNotificationsAllowed: false
};
Enter fullscreen mode Exit fullscreen mode

But what happens when your application grows?

const user1 = {
  // some random properties for user1
  permissions: {
    canRead: true,
    canWrite: false,
    canDelete: true,
    canInvite: false,
    canKick: true,
    canBan: false,
    canMute: true,
    canManageChannels: false,
    canManageRoles: true,
    canViewAuditLog: false,
    canManageServer: true,
    // ... and 20 more permissions
  }
}

const user2 = {
  // some random properties for user2
  permissions: {
    canRead: true,
    canWrite: false,
    canDelete: true,
    canInvite: false,
    canKick: true,
    canBan: false,
    canMute: true,
    canManageChannels: false,
    canManageRoles: true,
    canViewAuditLog: false,
    canManageServer: true,
    // ... and 20 more permissions
  }
}

const user3 = {
  // some random properties for user3
  permissions: {
    canRead: true,
    canWrite: true,
    canDelete: true,
    canInvite: true,
    canKick: true,
    canBan: true,
    canMute: true,
    canManageChannels: false,
    canManageRoles: true,
    canViewAuditLog: false,
    canManageServer: true,
    // ... and 20 more permissions
  }
}

// and a lot of users below..............

// Now you need to send this to the client
JSON.stringify([user1, user2, user3, ...]);
Enter fullscreen mode Exit fullscreen mode

Ouch! Look at all that data duplication! Each user object now carries around 30+ boolean properties just for permissions. When you scale to thousands of users, you're sending megabytes of redundant data over the network.

The Real Problems with Boolean Objects:

  1. Data Bloat: Each permission becomes a separate property in JSON
  2. Network Inefficiency: Sending "canRead": true 14 times
  3. Serialization Overhead: More data to parse and stringify

Good enough

Bitwise operations

Then you discover the world of bitwise flags - a technique that's been used in systems programming for decades. You define each permission as a power of two:

const Permissions = {
  READ: 1 << 0,    // 1 (0001)
  WRITE: 1 << 1,   // 2 (0010) 
  DELETE: 1 << 2,  // 4 (0100)
  INVITE: 1 << 3,  // 8 (1000)
};

// Combine permissions with bitwise OR
let userPermissions = Permissions.READ | Permissions.WRITE; // 3 (0011)

// Check permissions with bitwise AND
if (userPermissions & Permissions.READ) {
  // User can read
}

// Add permissions
userPermissions |= Permissions.DELETE;

// Remove permissions  
userPermissions &= ~Permissions.WRITE;
Enter fullscreen mode Exit fullscreen mode

This is much better! Now we're storing everything in a single number. But there are still challenges:

  1. Magic numbers everywhere - what does 15 mean again?
  2. No type safety - easy to mix different permission sets
  3. Limited to 32 bits - traditional numbers have limits. Of course, this problem can be solved by bigint, but this is not enough.
  4. Error-prone manual management - off-by-one errors in bit shifting
  5. Poor debugging experience - console.log(permissions) shows 27, not human-readable

And this is where bitwise-flag comes in...

npm install bitwise-flag
Enter fullscreen mode Exit fullscreen mode

Meet bitwise-flag - a library that brings type safety, immutability, and developer happiness to bitwise operations. You don't need to worry about bits. This library make this for you behind the scenes. Let's see how it solves our problems:

import { FlagsRegistry } from 'bitwise-flag';

// Define your permissions once
const Permissions = FlagsRegistry.from(
  'READ',    // 1n (0001)
  'WRITE',   // 2n (0010) 
  'DELETE',  // 4n (0100)
  'INVITE',  // 8n (1000)
  // ... add up to 100+ permissions without breaking a sweat
);

// No more magic numbers - everything is type-safe!
const userPermissions = Permissions.combine('READ', 'WRITE');

// Readable checks with autocomplete
if (userPermissions.has('READ')) {
  // TypeScript knows this is a valid permission!
}

// Compile-time safety - this will throw an error:
// userPermissions.has('NON_EXISTENT_PERMISSION'); ❌
Enter fullscreen mode Exit fullscreen mode

1. Network Efficiency Rediscovered

Remember our JSON bloat problem? Watch this:

// Before: 2KB of boolean properties
const user = {
  // some random properties for user...
  permissions: {
    canRead: true,
    canWrite: true,
    canDelete: false,
    // ... 20 more properties
  }
};

// After: 8 bytes total
const user = {
  permissions: 3n  // Just a single BigInt representing READ + WRITE
};

// And it's still fully type-safe when you parse it back:
const parsedPermissions = Permissions.parse(user.permissions); // READ + WRITE
console.log(parsedPermissions.has('READ')); // true
Enter fullscreen mode Exit fullscreen mode

2. Developer Experience That Doesn't Suck

// No more bitwise operator headaches
const adminPermissions = Permissions.combine('READ', 'WRITE', 'DELETE', 'INVITE');

// Human-readable debugging
console.log(adminPermissions.toString()); 
// "Flag([READ+WRITE+DELETE+INVITE]: 15)"

// Easy modifications (immutable by design)
const readOnlyAdmin = adminPermissions.remove('WRITE', 'DELETE');

// Type-safe bulk operations
const requiredPermissions = ['READ', 'WRITE'] as const;
const userFlags = Permissions.combine(...requiredPermissions);
Enter fullscreen mode Exit fullscreen mode

3. Debugging That Doesn't Make You Cry

// Traditional bitwise value - what does 3 mean?
console.log(userPermissions.value); // 3

// bitwise-flag - crystal clear
console.log(userPermissions.toString()); 
// "Flag([READ+WRITE+DELETE+INVITE]: 3)"

console.log(userPermissions.alias);
// "[READ+WRITE+DELETE+INVITE]"
Enter fullscreen mode Exit fullscreen mode

When You Should (and Shouldn't) Use Bitwise Flags

Perfect Use Cases:

  • Permission systems with 10+ possible flags
  • Feature toggles in large applications
  • Status combinations (e.g., "pending + approved + archived")
  • Network-sensitive applications where payload size matters
  • Real-time systems requiring fast permission checks

When to Stick with Simpler Solutions:

  • 1-3 simple boolean flags: just use plain booleans
  • Frequently changing flag sets: recompilation needed for type changes
  • Teams unfamiliar with bitwise concepts: learning curve exists

Check out the GitHub repository for complete documentation, more examples, and contribution guidelines.


Have you used bitwise flags in your projects? What challenges did you face? Share your experiences in the comments below!

Top comments (0)