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"];
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
}
What's problem here?
- Performance issues: Array lookups are O(n) - slow for large sets.
- No type safety - it's too easy to make a typo and TypeScript won't help you!
- 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
};
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, ...]);
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:
- Data Bloat: Each permission becomes a separate property in JSON
-
Network Inefficiency: Sending
"canRead": true14 times - 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;
This is much better! Now we're storing everything in a single number. But there are still challenges:
-
Magic numbers everywhere - what does
15mean again? - No type safety - easy to mix different permission sets
-
Limited to 32 bits - traditional numbers have limits. Of course, this problem can be solved by
bigint, but this is not enough. - Error-prone manual management - off-by-one errors in bit shifting
-
Poor debugging experience -
console.log(permissions)shows27, not human-readable
And this is where bitwise-flag comes in...
npm install bitwise-flag
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'); ❌
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
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);
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]"
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)