Module F-5 — Bit Manipulation: Flags, Permissions, and Fast Math
What this module covers: Every number in your program is already a sequence of bits — this module is about working with that sequence directly instead of through the abstraction of arithmetic. It covers the core bitwise operators, how a single integer can store dozens of independent boolean flags, and the handful of bit tricks that show up in permission systems, feature flags, and performance-sensitive code.
Binary, Briefly
Every integer in memory is a sequence of bits — 1s and 0s. The number 13 is 1101 in binary: reading right to left, that's 1 + 0 + 4 + 8 = 13 (each position worth double the one before it, starting at 1). You don't need to convert by hand often, but recognizing that an integer is just a fixed-width row of switches is the entire mental model this module builds on.
typescript
The Bitwise Operators
Five operators work directly on the bit representation, comparing or shifting bit-by-bit rather than treating the number as a single value:
typescript
Analogy: Think of AND, OR, and XOR as three different ways of comparing two rows of light switches, position by position. AND: a light turns on only if both switches at that position are on. OR: on if either is on. XOR: on only if they disagree — one on, one off.
XOR is the one worth sitting with, because its "only when they disagree" property is what makes it useful for toggling: XOR-ing a bit with 1 flips it, and XOR-ing a bit with 0 leaves it unchanged — which is exactly the mechanic bit flags rely on next.
Bit Flags: One Integer, Many Booleans
Instead of storing five separate boolean fields, you can pack up to 32 independent true/false facts into a single integer, one per bit position — commonly called a bitmask.
typescript
Each permission gets its own bit position (1 << 0, 1 << 1, 1 << 2...), so combining them with | never overwrites another flag — they occupy entirely separate positions in the same integer. Checking with & isolates just that one bit; every other bit is masked out to zero, so the comparison only ever reflects the flag you asked about.
Why this matters in production: a role/permission system with a handful of booleans stored as five separate boolean columns costs five columns, five reads, and five separate comparisons every time you check "can this user do X." As a single integer, storage is one column, and checking any combination of permissions is a single bitwise comparison — no matter how many flags you add, later, without a schema migration for each one.
Feature Flags as Bits
The same pattern shows up for feature toggles — a single integer field on a user or tenant record can represent "which experimental features are enabled for this account," checked and updated exactly like the permission bits above:
typescript
This is a lightweight, self-contained pattern for a handful of flags on your own schema. Full feature-flagging platforms (LaunchDarkly, GrowthBook, Statsig) solve a much bigger problem — targeting rules, percentage rollouts, real-time updates without a deploy — but at the storage layer, some of them still boil a user's resolved flag set down to exactly this kind of compact bitmask.
Fast Math with Shifts
Left shift multiplies by 2 per position; right shift divides by 2 per position (discarding the remainder, since bits below the decimal point don't exist in an integer). Because shifting operates directly on the bit pattern rather than going through general-purpose arithmetic, it's a common low-level optimization in code where every cycle counts:
typescript
isPowerOfTwo is a genuinely useful trick beyond micro-optimization — it shows up any time you're validating buffer sizes, chunk sizes, or hash table capacities (Module F-4), which are conventionally kept as powers of two specifically because it makes the modulo-by-bucket-count step reducible to a fast bitwise AND instead of a division.
Two more small, common ones:
typescript
The XOR swap is a genuinely clever trick (three XORs cancel out to swap two values) — but worth naming honestly: in modern TypeScript, [x, y] = [y, x] destructuring is just as fast in practice and vastly more readable. The XOR version is worth knowing because it appears in older codebases and interview settings, not because it's what you should reach for in new code.
Where This Shows Up in Your Stack
Unix file permissions are exactly this pattern at the OS level — chmod 755 is three octal digits, each one a 3-bit mask of read (4) / write (2) / execute (1), combined with the same OR logic used above.
Database permission and role fields: a permissions INTEGER column storing a bitmask avoids a wide, ever-growing table of boolean columns (or a many-to-many join table) for what's fundamentally a fixed, small set of capabilities — common in older or performance-sensitive schemas, though many modern systems now prefer a normalized roles table for readability and extensibility. Knowing both patterns lets you recognize an existing bitmask column instead of mistaking it for an inscrutable magic number.
Hash table capacity (Module F-4): keeping bucket counts as a power of two lets the engine compute hash & (capacity - 1) instead of hash % capacity — a bitwise AND instead of a division, which is measurably cheaper at the scale hash tables operate on.
Binary protocols and encodings: parsing a network packet header, a WebSocket frame, or a compact binary format (protobuf-adjacent, custom TCP framing) routinely involves shifting and masking bytes out of a buffer — this module's operators are the literal toolkit for that, not just an abstraction.
Summary
An integer is a fixed-width row of bits. Bitwise operators work on that representation directly: AND (&), OR (|), XOR (^), NOT (~), left shift (<<), right shift (>>).
Bit flags pack many independent booleans into one integer — one bit position per flag, combined with |, checked with &, cleared with &= ~flag, toggled with ^=.
Shifts implement fast multiply/divide by powers of two, and the n & (n - 1) === 0 trick checks "is this a power of two" in one operation.
The XOR swap is a curiosity, not a recommendation — destructuring assignment is equally fast and far more readable in modern TypeScript.
This shows up at every layer of your stack — Unix permission bits, database permission columns, hash table bucket sizing, and binary protocol parsing all lean on exactly these operators.
The next module returns to the sequential structures this course started with — Stacks: LIFO, the Call Stack, and Expression Parsing — the structure quietly running underneath every function call you've written so far.
Knowledge Check
Given permissions = CAN_READ | CAN_WRITE where CAN_READ = 1 and CAN_WRITE = 2, why does (permissions & CAN_DELETE) !== 0 correctly evaluate to false even though permissions is a single combined integer?
Why is n & (n - 1) === 0 (for n > 0) a correct test for "is n a power of two"?
According to the module, why is the XOR swap trick (x ^= y; y ^= x; x ^= y;) presented as "a curiosity, not a recommendation" for modern TypeScript code?
Test your knowledge with more question sets
Sign in to access a wider variety of questions and get notified when new practice sets are added to this module.
const a =0b1100;// 12const b =0b1010;// 10a & b;// 0b1000 = 8 — AND: 1 only where BOTH bits are 1a | b;// 0b1110 = 14 — OR: 1 where EITHER bit is 1a ^ b;// 0b0110 = 6 — XOR: 1 where the bits DIFFER~a;// -13 — NOT: flips every bit (and flips the sign, due to two's complement)a <<1;// 0b11000 = 24 — left shift: every bit moves left, multiplying by 2 per shifta >>1;// 0b0110 = 6 — right shift: every bit moves right, dividing by 2 per shift (rounding down)
constCAN_READ=1<<0;// 0b0001 = 1constCAN_WRITE=1<<1;// 0b0010 = 2constCAN_DELETE=1<<2;// 0b0100 = 4constCAN_ADMIN=1<<3;// 0b1000 = 8// Grant read + write by combining with ORlet permissions =CAN_READ|CAN_WRITE;// 0b0011 = 3// Check a specific permission with ANDconst hasWriteAccess =(permissions &CAN_WRITE)!==0;// trueconst hasAdminAccess =(permissions &CAN_ADMIN)!==0;// false// Add a permission without disturbing the otherspermissions |=CAN_DELETE;// now 0b0111 = 7// Remove a permission without disturbing the otherspermissions &=~CAN_WRITE;// clears just the WRITE bit -> 0b0101 = 5// Toggle a permission (on becomes off, off becomes on)permissions ^=CAN_READ;// flips just the READ bit
functionfastMultiplyBy8(n:number):number{return n <<3;// equivalent to n * 8 (2^3), but computed via shifting}functionfastDivideBy4(n:number):number{return n >>2;// equivalent to Math.floor(n / 4)}functionisPowerOfTwo(n:number):boolean{return n >0&&(n &(n -1))===0;// A power of two has exactly one bit set (e.g. 8 = 0b1000).// Subtracting 1 flips that bit and every bit below it to 1 (7 = 0b0111).// AND-ing the two together always produces 0 — for any other number, it won't.}
functioncountSetBits(n:number):number{let count =0;while(n >0){ count += n &1;// check the lowest bit n >>=1;// shift everything right by one}return count;}functionswapWithoutTemp(pair:[number,number]):[number,number]{let[x, y]= pair; x ^= y; y ^= x; x ^= y;return[x, y];// swapped, without a third temporary variable}