One Number to Rule the Code: Bitmasking That Scales the Load by Hardik Raja on August 25, 2025 120 views

The client asked for a new role type with some custom permissions.

I opened the PermissionFlag enum, added a few lines, pushed the code…

and went to grab a coffee. ☕

The change took a minute. Literally.

It wasn’t always this simple. What now takes minutes used to take days—modifying the database, adjusting backend logic, and updating the UI for even the smallest change. Each new role or action added layers of complexity.

But with the bitmask pattern, everything changed.

Bitmask is a number where each bit (0 or 1) shows if something is on or off. This way, one number can keep track of many true/false values at once. That’s bitmasking in a nutshell.

This lightweight approach has not only simplified permission handling but opened the door to flexible, scalable design across the system. And this isn’t just about backend tweaks or permission models—it’s about building systems that scale effortlessly. As applications grow, so do their needs. More roles, more toggles, more boolean flags—until your data model becomes bloated and brittle.

Bitmasking helped us move from a forest of boolean fields to a single, dynamic value. It’s a technique that’s:

  • Simple to implement
  • Easy to maintain
  • Clean and efficient across frontend and backend

We’ll walk through this with a real-world example of role-based permissions—but the takeaway is much broader: this is a strategy for designing systems that embrace change without breaking down.

The Pain of Managing Permissions with Booleans

Imagine a hotel management app with menus like Guest Check-In, Billing, and Housekeeping. The owner needs precise control over actions on each menu based on different role types. At the foundation, the system defines three primary role types:

  • Manager
  • Assistant Manager
  • Helper

Under these, specific roles will be created in the future. Each menu of the application allows users to perform three possible actions: Create record, Edit record, and View record.

To support every possible configuration for each menu, the system initially used nine boolean columns in the menu table—one for each (RoleType × Action) combination:

  • manager_create, manager_edit, manager_view
  • assistant_manager_create, assistant_manager_edit, assistant_manager_view
  • helper_create, helper_edit, helper_view

Now, if the hotel owner wants to configure the Guest Check-In menu such that:

  • Manager role type has create access
  • Assistant Manager role type has edit access
  • Helper role type has view access

When a role is granted create access, the system automatically includes edit and view as underlying actions—and similarly, granting edit also implies view access.

Then the corresponding row in the menu table might look like this:.

menu_namemanager_createassistant_manager_edithelper_viewRemaining six actions
Guest Check-Intruetruetruefalse

This hardcoded structure may work well when the set of role types is fixed. But the moment new role types are introduced or existing ones evolve, it turns into a feature development task rather than a simple configuration update. Each change in role type meant:

  • Schema modifications and database migrations
  • Code updates across entities/models and service layers
  • Adjustments to validation logic
  • And extensive rounds of testing and regression checks

So the real question became—how can we design it in a way that a simple change feels like magic and just works across the system?

Bitmasks to the Rescue

What we really needed was a way to store configuration in the database—without hardcoding role types across multiple columns.

Ideally, a single value should capture everything. And since each permission is essentially a yes or no, the answer was obvious: use a binary number.

How Bitmasking Works: A Practical Walkthrough

Before we jump into the example, let’s understand how the bitmask actually works.

Each permission—defined as a combination of RoleType × Action—is assigned a unique bit index. These bits are aligned in reverse order, with index 0 representing the rightmost bit in the binary string.

Here’s how all possible combinations map to bit positions:

Bit IndexRoleTypeAction
0ManagerCreate
1ManagerEdit
2ManagerView
3Assistant ManagerCreate
4Assistant ManagerEdit
5Assistant ManagerView
6HelperCreate
7HelperEdit
8HelperView

So, a binary string like:

000000011

Would mean:

  • Bit 0 (Manager–Create) = ✅
  • Bit 1 (Manager–Edit) = ✅
  • All others = ❌

Now, let’s say the hotel owner wants to configure the Guest Check-In menu with the following permissions for role type:

  • Manager can create new guest entries.
  • Assistant Manager can edit existing entries.
  • Helper can view entries, but not create or edit.

We assign each (RoleType × Action) to a specific bit position, from 0 (rightmost) to 8 (leftmost):

Bit PositionRoleType–Action
0Manager–Create
4AssistantManager–Edit
8Helper–View

Setting these three bits gives us the binary mask:

Position: 8 7 6 5 4 3 2 1 0 Bits: 1 0 0 0 1 0 0 0 1

Reading 100010001 as a decimal number yields 273 (256 + 16 + 1). This single integer value encodes all three permissions:

  • Bit 0 → Manager–Create (1)
  • Bit 4 → AssistantManager–Edit (16)
  • Bit 8 → Helper–View (256)

By storing 273 in the permission_mask column of menu, we know exactly which RoleTypes can perform which actions.

Effortless Scalability: Adding New Roles Without Breaking Anything

Tomorrow, the hotel owner might introduce a new RoleType—Supervisor—with Create access to the Guest Check-In menu.

In the bitmask model, each permission maps to a fixed bit position, starting from the right (bit 0). To ensure compatibility with existing data, new permissions are always added to the left, preserving all previous mappings.

So when Supervisor is added, its actions—Create, Edit, and View—are assigned to bits 9, 10, and 11.

Initially, the system used a 9-bit binary number to represent permissions:

000000000

When a new RoleType like Supervisor is added, its three permissions (Create, Edit, View) are prepended to the existing bitmask, not appended. This expands the binary representation to 12 bits, like so:

xxx000000000

To support the requirement where the Supervisor RoleType can have Create access to the Guest Check-In Menu.

  • The current permission mask of Guest Check-In menu is 273 (binary 100010001)
  • Supervisor–Create maps to bit 9, which equals 512 (binary 001xxxxxxxxx)
  • The updated mask becomes 273 + 512 = 785 (binary 1100010001)

This approach guarantees complete backward compatibility.

Any menu entry with the existing value 273 will continue to function exactly as before. And whenever the updated value 785 is applied—such as for the Guest Check-In menu—Supervisor’s Create permission is seamlessly recognized.

No breakage. No rewrites. Just smooth, controlled evolution.

The Single Source Of Truth

Now, the real power lies in how this logic flows through the code.

If you look closely, everything in this approach is flexible and lightweight—except for one crucial anchor: bit index binding. Each permission relies on its position in the binary sequence, which means we need a consistent reference for those indexes.

This binding must be defined somewhere—either in the database or in code. For our case, we chose to manage it cleanly through an enum.

And here’s the trick: the ordinal of each enum constant becomes its index in the binary string. That means:

  • The first constant (ordinal = 0) is bit 0 (rightmost)
  • The second is bit 1, and so on
public enum PermissionFlag {
    MANAGER_CREATE,
    MANAGER_EDIT,
    MANAGER_VIEW,
    ASSISTANT_MANAGER_CREATE,
    ASSISTANT_MANAGER_EDIT,
    ASSISTANT_MANAGER_VIEW,
    HELPER_CREATE,
    HELPER_EDIT,
    HELPER_VIEW;
}

This enum becomes the single source of truth. It drives everything—from populating permissions on the screen to validating them when data is submitted.

Let’s walk through how it works.

When the hotel owner assigns permissions to a Manager role for various menus, the backend uses the permission_mask—a decimal value stored in the database. This number is converted to binary, and each bit maps to a specific combination defined in the enum.

For example, when an Hotel Owner assigns permissions to a some manager type Role, the UI can dynamically load all enum values, display them as checkboxes.

Using this mapping, the UI automatically renders permissions as enabled, disabled. For example:

MenuCreateEditView
Payment Gateway Settings🚫🚫🚫
Email Settings🚫🚫🚫
Guest Check-In
Restaurant Table Booking🚫

  • 🚫 (Disabled): Not applicable for this role type
  • (Unchecked Checkbox): Can be checked by the hotel owner, as permissions are granted using a decimal number.

So in this case:

  • Email Settings and Payment Gateway Settings are completely disabled for the Manager—these menus are tied to a different RoleType (like Owner).
  • For Guest Check-In, Create, Edit and View permission is accessible.
  • In Restaurant Table Booking, Edit and View is granted for manager role type.

A Design That Scales: SOLID Principles in Action

This approach doesn’t just make things flexible—it follows strong architectural principles too. Most notably, it adheres to the Single Responsibility Principle and the Open/Closed Principle from the SOLID design model:

  • Single Responsibility Principle (SRP): All permission logic lives in one place—PermissionFlag. No scattered conditionals, no duplicated logic.
  • Open/Closed Principle (OCP): The system is open for extension (you can add new RoleTypes and actions), but closed for modification (existing data and logic don’t need to be changed).

Your enum acts as the abstraction layer, the binary mask acts as the data model, and all permission behavior aligns with clean, scalable code.

Bitmask Limits and Data Type Choices

As powerful as bitmasks are, the number of bits you can store depends on the data type used. Here’s how to choose wisely:

  • Use int (32 bits) when your system has 32 or fewer permissions.
  • Switch to long (64 bits) if you expect up to 64 combinations.
  • For cases with more than 64 permissions, use BigInteger in Java. If your database or target language doesn’t support large integers, consider storing the binary string representation instead.

This keeps your system future-ready, without risking overflow or data loss as new RoleTypes or actions are introduced.

Beyond Permissions: A Reusable Pattern for Boolean Explosion

This story was about role-based permissions—but the core idea extends far beyond that. Bitmasking is a versatile pattern you can use anytime you’re dealing with multiple yes/no flags that grow over time.

If your system has ever faced a situation like:

  • A growing list of feature toggles
  • Multiple capabilities assigned to users, devices, or plans
  • Tracking status combinations (e.g., active, locked, pending approval, archived)
  • Logging or storing event flags compactly
  • Managing access rules in IoT systems, game levels, hardware capabilities

Then bitmasking can give you a clean, scalable way to manage it.

Instead of adding more columns or fields, you maintain a single compact integer and decode it using well-structured logic—often through enums or constants.

🏁 Final Thoughts : Think in Bits, Not Columns

Designing systems that scale effortlessly is not just about choosing the right tech—it’s about recognizing patterns. Bitmasking is one of those patterns that hides in plain sight but unlocks enormous power once embraced.

So next time you’re tempted to add that tenth boolean column… pause—and think in bits. 😉

🔗 Explore the Code

Curious to see this pattern in action?

Check out the working proof of concept (POC) on GitHub:

📂 Repository: github.com/Hardikraja/bitmask-pattern

This is a simple terminal-based Java demo that illustrates how to implement and manage permissions using bitmasking.

Feel free to explore, experiment, and adapt it to your needs!

Happy Coding!

⚡Fast, Single, and Smart: The Truth Behind JavaScript’s Speed

About Author

Hardik Raja

Lead - Solution Analyst

Hi, I’m Hardik Raja – I enjoy designing solutions and building software architectures that are clear, scalable, and easy to maintain. I like turning ideas into structured systems where every piece fits together with purpose, and I focus on creating solutions that add real value to the people and teams who use them.