Introduction
In computer science theory, a boolean is the simplest data type: it’s either a 0 or a 1. It represents a single bit of information.
But in practice, if you declare a boolean variable in almost any high-level programming language—JavaScript, Python, Java—it takes up at least one full byte (8 bits) of memory. Often, due to object overhead and memory alignment, it takes up even more.
This is a small inefficiency that we usually ignore. Memory is cheap, right? But this discrepancy—between what a boolean is and how it’s stored—bugged me. It’s a paradox of modern abstraction: to make things easy to use, we waste 87.5% of the space.
This small curiosity led me to build ByteFlags.
The Real Problem
Why does this matter? For a simple "ToDo" app, it doesn't.
But consider a system where state is everywhere.
-
Feature Flags: A user might have
isBetaTester,hasDarkTheme,emailVerified,notificationsEnabled,marketingOptIn, etc. -
Game Development: An entity has states like
isJumping,isGrounded,isInvincible,isDead. -
Permissions:
canRead,canWrite,canDelete,canExecute.
If you store these as separate boolean properties on an object, you are scattering these bits across bytes of memory. If you have millions of users or thousands of entities, that wasted space adds up. More importantly, managing unrelated booleans as loose variables can lead to messy code. You end up passing around long lists of arguments or having massive configuration objects.
I wanted a way to group these related flags into a single, compact unit.
Theory vs. Reality
So why do languages do this? Why isn't a boolean just a bit?
The reality is that computer memory is byte-addressable. The CPU likes to fetch data in chunks—bytes, words (4 bytes), or double words (8 bytes). It doesn't have a direct address for "Bit #3 of Byte #1000".
To read a distinct bit, the computer has to:
- Fetch the whole byte.
- Filter out the other bits (masking).
- Check the result.
This is faster to do if the boolean just claims the whole byte for itself. The language designers made a trade-off: waste memory to save CPU cycles and simplify syntax.
As engineers, we accept this trade-off most of the time. But sometimes, checking that assumption is where the learning happens.
The Idea Behind ByteFlag
I decided to implement the "theory" manually. I wanted to pack up to 8 boolean flags into a single number (one byte, 0-255).
The concept is simple:
- Bit 0 represents Flag A
- Bit 1 represents Flag B
- ...and so on, up to Bit 7.
If I want flags A and C to be "on", I set the 0th and 2nd bits to 1.
0000 0101 in binary is 5 in decimal.
So instead of storing { A: true, B: false, C: true }, I just store the number 5.
To make this work, I needed bitwise operations:
-
OR (
|) to enable a flag. -
AND (
&) to check a flag. -
XOR (
^) to toggle a flag. -
NOT (
~) to disable a flag.
Implementation Overview
While bitwise math is efficient, it’s not readable. No one wants to see if (user.flags & 4) in production code. You forget what 4 means immediately.
My goal with ByteFlags was to hide the math behind a clean, human-readable API, while keeping the storage compact.
I built it in TypeScript to ensure type safety. Here is the high-level design:
-
Mapping: The user provides a map of names to bit positions.
const flags = new ByteFlags({ User: 0, Admin: 1 }); -
High-Level API: I exposed intuitive methods so you never have to touch a bitwise operator:
State Management:
*enable('Admin')– Sets the bit to 1.
*disable('Admin')– Sets the bit to 0.
*toggle('Admin')– Flips the bit.
*reset()– Clears all flags instantly.Querying:
*isEnabled('User')– Returnstrueorfalse.Serialization (The "Magic" Part):
One of the biggest pain points with bits is debugging. I added methods to make the byte human-readable:
*toJSON()– Returns{ User: true, Admin: false }.
*toBinaryString()– Returns"00000001".
*toHex()– Returns"0x01".
The complexity of (1 << index) is hidden. The developer just sees flags.enable('Admin').
What I Learned
Building this small library was a reflective process.
- Data Representation: It forced me to think about how data actually sits in memory. We get so used to JSON and Objects that we forget the underlying zeros and ones.
- The Cost of Abstractions: Every convenience in high-level languages has a cost. Usually, it's worth it, but knowing what you are paying makes you a better engineer.
- Tooling: Setting up the CI/CD pipeline, publishing to NPM, and generating documentation took more time than the code itself. "Finished" code is only 20% of a shipping product.
Conclusion
ByteFlags isn't going to replace your database or revolutionize your tech stack. It’s a micro-optimization.
But working on it reminded me that great engineering isn't always about building massive systems. Sometimes, it's about looking at the smallest unit of data—a boolean—and asking, "Why is this the way it is?"
If you are curious about bitwise operations or just want a tiny library to manage states, check out the code. It’s simple, readable, and does exactly one thing.

Top comments (0)