WHAT IS A BIT?
Memory Layout — Value 45 in 8 bits
Each bit position n carries weight 2n. Bit 0 = LSB (Least Significant Bit). Bit 7 = MSB (Most Significant Bit).Value = 0×128 + 0×64 + 1×32 + 0×16 + 1×8 + 1×4 + 0×2 + 1×1 = 45
DATA SIZES
| C Type | Java Type | Size | Bit Width | Unsigned Range |
|---|---|---|---|---|
uint8_t | byte | 1 byte | 8 bits | 0 – 255 |
uint16_t | short | 2 bytes | 16 bits | 0 – 65,535 |
uint32_t | int | 4 bytes | 32 bits | 0 – 4,294,967,295 |
uint64_t | long | 8 bytes | 64 bits | 0 – 18,446,744,073,709,551,615 |
TWO'S COMPLEMENT — SIGNED INTEGERS
Why Two's Complement?
Negate a number by flipping all bits then adding 1. This means addition hardware works identically for positive and negative numbers — no special case needed. The MSB is the sign bit: 0 = positive, 1 = negative. Range for N-bit signed: −2N−1 to 2N−1−1.Step 1 — Start with +45: 0 0 1 0 1 1 0 1 Step 2 — Flip all bits: 1 1 0 1 0 0 1 0 Step 3 — Add 1: 1 1 0 1 0 0 1 1 ← -45 = 0xD3
Verify: 128+64+16+2+1 = 211. 211 - 256 = -45 ✓
HEX ↔ BINARY QUICK REFERENCE
Conversion Examples
0xB5 = 1011 0101 = 1810xDEAD = 1101 1110 1010 1101Java: use
L suffix for long literals: 0xFFFFFFFFLC: always use
0x prefix — 0xFFFF is 65535.
INTEGER PROMOTION RULES (C)
Silent widening — the source of many bitmasking bugs
When you use a value smaller thanint in a bitwise expression, C automatically promotes it to int before the operation. Bitwise NOT on a uint8_t produces a 32-bit result — not an 8-bit one.
// Step 1 — x promoted to int: 0x00000001 // Step 2 — ~(0x00000001) = 0xFFFFFFFE (32-bit result) // Step 3 — truncated to u8 = 0xFE (OK by accident here)
// In a mask expression this silently bleeds through: uint8_t mask = 0x0F; if (~mask & 0xFF00FF00) { } // ~mask = 0xFFFFFFF0 — upper bits live!
// Safe fix: cast explicitly back uint8_t safe = (uint8_t)(~x); // Forces back to 8 bits
| Expression | What C actually does | Safe? |
|---|---|---|
~(uint8_t)x | Promote to int, flip 32 bits → 0xFFFFFFFE | ⚠️ Depends on use |
(uint8_t)(~x) | Same, but truncates result → 0xFE | ✅ Safe |
1 << 31 | Signed int shift — UB if overflow | ❌ UB! Use 1U << 31 |
1ULL << 40 | 64-bit unsigned shift — safe | ✅ Safe |
ENDIANNESS — NETWORK CODE GOTCHA
Big-Endian vs Little-Endian
Little-endian (x86, ARM default): LSB stored at lowest address.Big-endian (network byte order): MSB stored at lowest address.
Network protocols always use big-endian. Masking a multi-byte field without calling
ntohs()/ntohl() first will produce wrong results on x86.
Little-endian (x86): [0x34][0x12] ← LSB at lower address Big-endian (network): [0x12][0x34] ← MSB at lower address
Reading IPv4 total_length on x86 without conversion: uint16_t len = (uint16_t)ptr; // gives 0x3412 instead of 0x1234!
Correct: uint16_t len = ntohs((uint16_t)ptr); // always right
OVERFLOW AND WRAPAROUND
Unsigned Overflow — Well-Defined Wraparound
Unsigned integer overflow wraps modulo 2N. Exploited intentionally in checksums, ring buffers, and TCP sequence number arithmetic.Signed Overflow — Undefined Behaviour (C)
Signed integer overflow is UB in C. The compiler may assume it never happens and optimise away your overflow guards. Always use unsigned types for bit manipulation.// Safe patterns: uint32_t a = UINT32_MAX; uint32_t sum = a + 1; // wraps to 0 — defined ✓ // To detect: check BEFORE adding if (a > UINT32_MAX - b) { /* overflow */ }
// Shift safety: int x = 1 << 31; // UB on signed int! uint32_t x = 1U << 31; // Fine — unsigned ✓
THE 6 BITWISE OPERATORS
| Operator | Symbol | Rule | Key Use |
|---|---|---|---|
| AND | & | 1 only when BOTH bits are 1 | Filter/mask — forces bits to 0 |
| OR | | | 1 when AT LEAST ONE bit is 1 | Set (force to 1) specific bits |
| XOR | ^ | 1 when bits are DIFFERENT | Toggle bits; self-cancellation tricks |
| NOT | ~ | Flips ALL bits (unary) | Create inverse masks to CLEAR bits |
| Left Shift | << | Move bits toward MSB; fill 0s from right | Multiply by 2ⁿ; build masks with 1 << n |
| Right Shift | >> | Move bits toward LSB | Divide by 2ⁿ; extract upper fields |
AND — Extract lower nibble
OR — Set bit 5
XOR — Toggle bit 3
Properties: x ^ x = 0 (self-cancel) x ^ 0 = x (identity) x ^ y ^ y = x (self-inverse)
NOT — Inverse mask
In C: ~0 on signed int = -1 (all bits set) In Java: ~0 is also -1 (int always 32-bit)
SHIFTS — LOGICAL vs ARITHMETIC
Right Shift (>>): LOGICAL (unsigned) fills 0; ARITHMETIC (signed) fills sign bit LOGICAL: 0b10110100 >> 2 → 0b00101101 (fills 0) ARITHMETIC: 0b10110100 >> 2 → 0b11101101 (fills sign bit)
C: >> on unsigned = logical. >> on signed = implementation-defined (usually arithmetic) Java: >> = arithmetic, >>> = logical (unsigned right shift)
>>> when you want logical (zero-fill) shift on potentially-negative values.OPERATOR PRECEDENCE — THE HIDDEN BUG
==, !=, <, >). The expression x & mask == 0 is parsed as x & (mask == 0) — almost certainly not what you want.CORRECT — add parentheses: if ((x & mask) == 0) { … } // what you actually mean
Precedence (high → low): ~ NOT (unary — highest bitwise) << >> Shifts & AND ^ XOR | OR == != < > Comparisons ← sit ABOVE &, ^, | ← GOTCHA! && || Logical AND/OR = |= &= ^= Assignment (lowest)
| Expression | Parsed as | Correct form |
|---|---|---|
x & 0xFF == 0 | x & (0xFF == 0) = x & 0 = always 0 | (x & 0xFF) == 0 |
x | y > 0 | x | (y > 0) — adds 0 or 1 to x | (x | y) > 0 |
a ^ b == c | a ^ (b == c) — XORs with boolean | (a ^ b) == c |
flags & FLAG_A != 0 | flags & (FLAG_A != 0) = flags & 1 | (flags & FLAG_A) != 0 |
THE 4 CORE MASK OPERATIONS
| Operation | Formula | Effect |
|---|---|---|
| SET | x |= (1 << n) | Force bit n to 1 |
| CLEAR | x &= ~(1 << n) | Force bit n to 0 |
| TOGGLE | x ^= (1 << n) | Flip bit n |
| CHECK | (x >> n) & 1 | Read bit n — returns 0 or 1 |
x |= (1 << 7); // SET bit 7 x &= ~(1 << 6); // CLEAR bit 6 x ^= (1 << 4); // TOGGLE bit 4 int b3 = (x >> 3) & 1; // CHECK bit 3 → 0 or 1
MULTI-BIT MASK CONSTRUCTION
Formula: mask for width bits starting at pos
mask = ((1 << width) - 1) << pos
Step 1 — (1 << 3) = 0b1000 Step 2 — subtract 1 = 0b0111 ← 3 consecutive ones Step 3 — shift to pos 13:
Bit: 15 14 13 12 11 … 0 mask: 1 1 1 0 0 … 0 = 0xE000
mask = ((1 << 3) - 1) << 13 = 0xE000
PATTERN INSERTION — READ/WRITE A BITFIELD
3-Step: Clear target bits → OR in new pattern
Goal: Replace bits 15–13 with pattern0b110
STEP 1 — Build mask: mask = ((1<<3)-1) << 13 = 0xE000 STEP 2 — Clear target: x & ~mask = 0000 0100 1101 0010 STEP 3 — OR in pattern: pattern = 0b110 << 13 = 0xC000 result = 1100 0100 1101 0010 bits 15,14,13 = 1,1,0 ✓
EXTRACT A BITFIELD
~mask on a 16-bit value may sign-extend to 32-bit. Cast explicitly: (uint16_t)(~mask) to be safe in mixed-width expressions.BITMASK AS A SET — UNION, INTERSECTION, DIFFERENCE
A bitmask of N bits represents a subset of N elements
Bit i = 1 means element i is in the set. Bit i = 0 means it is absent. This abstraction drives subset-sum DP, scheduling, permutation states, and flag systems.| Set Operation | Formula | Meaning |
|---|---|---|
| Union | A | B | Elements in A or B (or both) |
| Intersection | A & B | Elements in both A and B |
| Difference A − B | A & ~B | Elements in A but not in B |
| Complement | ~A & FULL | All elements NOT in A (where FULL = (1<<N)−1) |
| Is subset? | (A & B) == A | Every element of A is also in B |
| Is empty? | A == 0 | Set has no elements |
| Full set (N bits) | (1 << N) - 1 | All N elements present |
| Add element i | A | (1 << i) | Include element i in A |
| Remove element i | A & ~(1 << i) | Exclude element i from A |
sub = (sub−1) & S iterates all non-empty subsets of S efficiently. This is the cornerstone of bitmask DP for problems like TSP, task scheduling, and subset-sum variants.THE CANONICAL BIT TRICKS
🟢 Is Power of Two?
Non-power (20): 0001 0100 19: 0001 0011 AND: 0001 0000 ← non-zero
🔴 Clear Lowest Set Bit
🔵 Isolate Lowest Set Bit
Why: -x flips all bits above the lowest 1, leaves the 1 intact; AND zeros the rest.
🔶 Find MSB Position
MORE CLASSIC ONE-LINERS
(x << k) | (x >> (32-k)) is what most compilers recognize and compile to a single ROL / ROR instruction.🟣 Find Position of Lowest Set Bit
__builtin_ctz(x) = count trailing zeros = bit position ctz(0b10110100) = 2 ← position of lowest ‘1’
// Portable C (no builtins) — use isolate-then-popcount: int pos = __builtin_popcount((x & -x) - 1);
Next Power of Two
x = 0b0101 1100 (92) After smearing: 0b0111 1111 (127) Add 1: 0b1000 0000 (128) ✓
UNIVERSAL BITFIELD FORMULA REFERENCE
INSERT a bitfield (write pattern into bits [start .. start+width-1]): mask = ((1 << width) - 1) << start value = (value & ~mask) | ((pattern & ((1 << width)-1)) << start)
CHECK if any bit in range is set: (value & mask) != 0
COUNT set bits in range: popcount((value >> start) & ((1 << width) - 1))
REVERSE BITS — DIVIDE AND CONQUER
Strategy: Divide-and-Conquer Swap
Each level swaps progressively smaller chunks: 16-bit halves → 8-bit bytes → 4-bit nibbles → 2-bit pairs → individual bits. Five passes, no loops. The masks are alternating patterns:0xAAAAAAAA = 1010...1010, 0x55555555 = 0101...0101.
GRAY CODE
// Gray code → Binary int fromGray(int g) { int n = 0; for (; g > 0; g >>= 1) n ^= g; return n; }
⚡ Real-World Context: DPDK rte_mbuf.ol_flags
ol_flags is a 64-bit integer on every DPDK packet with 30+ defined flag bits controlling TX/RX offloads, tunneling, timestamps, and security metadata. Every packet in a SASE dataplane engine uses exactly this pattern.
ol_flags |= PKT_RX_VLAN | PKT_RX_RSS_HASH; // Set multiple flags if (ol_flags & PKT_TX_TCP_SEG) { /* TSO path */ } // Check one flag
ol_flags &= ~PKT_RX_VLAN; // Clear a flag uint64_t both = PKT_RX_VLAN | PKT_RX_RSS_HASH; if ((ol_flags & both) == both) { /* both set */ } // Check if BOTH set
IPv4 HEADER BITFIELDS
Bit: 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 ┌───┬───┬───┬────────────────────────────┐ │ 0 │DF │MF │ Fragment Offset (13 bits) │ └───┴───┴───┴────────────────────────────┘ Reserved More Frags Don’t Fragment
Extract Fragment Offset: offset = ntohs(ip->frag_off) & 0x1FFF Check Don’t Fragment bit: df = ntohs(ip->frag_off) & 0x4000
C BITFIELD STRUCTS vs EXPLICIT MASKS
TCP FLAGS FIELD
Bit: 8 7 6 5 4 3 2 1 0 ┌────┬────┬────┬────┬────┬────┬────┬────┬────┐ │ NS │CWR │ECE │URG │ACK │PSH │RST │SYN │FIN │ └────┴────┴────┴────┴────┴────┴────┴────┴────┘
Common patterns: SYN = 0x002 ← connection initiation SYN+ACK = 0x012 ← server handshake reply ACK = 0x010 ← data acknowledgement FIN+ACK = 0x011 ← graceful connection close RST = 0x004 ← abrupt reset / port closed
if (flags & TCP_SYN) { // new connection } if ((flags & (TCP_SYN | TCP_ACK)) == (TCP_SYN | TCP_ACK)) { // handshake } if (flags & TCP_RST) { // reset — drop state } if (flags & TCP_FIN) { // graceful close }
VLAN 802.1Q TAG — COMPLETE EXTRACTION
Bit: 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 ┌──────────┬───┬──────────────────────────────────────┐ │ PCP (3) │DEI│ VID (12 bits) │ └──────────┴───┴──────────────────────────────────────┘ Priority Drop VLAN ID (0–4095) Code Point Elig.
PCP = bits [15:13] — 3-bit 802.1p QoS priority (0–7, 7 = highest) DEI = bit [12] — Drop Eligible Indicator VID = bits [11:0] — VLAN ID (VID 0=untagged, 4095=reserved)
ntohs() before extracting fields from a live packet.PATTERN RECOGNITION CHEAT SHEET
| When You See... | Think... | Key Formula |
|---|---|---|
| Find unique / missing element | XOR pair cancellation | result ^= each element |
| No extra space, O(n) | XOR trick | x ^ x = 0, x ^ 0 = x |
| Is power of two? | Clear lowest set bit | x > 0 && (x & (x-1)) == 0 |
| Count set bits | Brian Kernighan | x &= x-1 in loop |
| Set/clear/toggle one bit | OR / AND~mask / XOR | 1 << n as mask |
| Extract N bits from position P | Shift + mask | (x >> P) & ((1<<N)-1) |
| Insert pattern at position P | Clear-then-OR | (x & ~mask) | (pat << P) |
| Consecutive elements differ by 1 bit | Gray code | n ^ (n >> 1) |
| Hamming distance | popcount of XOR | __builtin_popcount(x^y) |
CORE INTERVIEW SOLUTIONS
🧠 Find Single Number — LeetCode 136
Every element appears twice except one. XOR cancels pairs:x^x=0.
🧠 Find Missing Number
Array contains 0..n with one missing. XOR all indices with all values.🧠 Two Elements Appearing Once (others twice)
XOR all → get x^y. Use rightmost differing bit to partition array into two groups.🧠 Count Bits for 0..n — LeetCode 338
DP:dp[i] = dp[i >> 1] + (i & 1). Removes LSB and adds it back.
MORE INTERVIEW PATTERNS
🧠 Single Number II — LeetCode 137 (each element appears 3 times)
XOR cancels pairs but not triplets. Use two variables (ones, twos) to track per-bit modulo-3 count. The single number accumulates in ones.
🧠 Hamming Distance — LeetCode 461 & Total Hamming Distance LC 477
Number of positions where two values differ = popcount of their XOR.// Total Hamming distance across all pairs in array — O(32n) int totalHammingDistance(int[] nums) { int total = 0; for (int bit = 0; bit < 32; bit++) { int ones = 0; for (int n : nums) ones += (n >> bit) & 1; total += ones * (nums.length - ones); // pairs that differ at this bit } return total; }
🧠 Bitwise AND of Range [m, n] — LeetCode 201
AND of all numbers in [m, n]. Any bit that flips across the range becomes 0. Only the common prefix of m and n survives.OPERATOR SUMMARY
| Operator | Symbol | Effect | Example |
|---|---|---|---|
| AND | & | 1 only if both 1 — masks/filters | 0b1100 & 0b1010 = 0b1000 |
| OR | | | 1 if at least one 1 — sets bits | 0b1100 | 0b0011 = 0b1111 |
| XOR | ^ | 1 if bits differ — toggles/compares | 0b1100 ^ 0b1010 = 0b0110 |
| NOT | ~ | Flips all bits | ~0b00001111 = 0b11110000 |
| Left Shift | << | Shift left, fill 0 from right | 0b0001 << 3 = 0b1000 |
| Right Shift | >> | Shift right (logical/arithmetic) | 0b1000 >> 2 = 0b0010 |
CORE PATTERNS CHEATSHEET
| Goal | Code |
|---|---|
| Set bit n | x |= (1 << n) |
| Clear bit n | x &= ~(1 << n) |
| Toggle bit n | x ^= (1 << n) |
| Check bit n | (x >> n) & 1 |
| Is power of two? | x > 0 && (x & (x-1)) == 0 |
| Clear lowest set bit | x & (x - 1) |
| Isolate lowest set bit | x & (-x) |
| Count set bits | while(x) { x &= x-1; count++; } |
| Extract field [p, p+w) | (x >> p) & ((1<<w)-1) |
| Insert pattern at p | (x & ~mask) | (pat << p) |
| Rotate left k | (x << k) | (x >> (32-k)) |
| XOR unique element | result=0; for n: result ^= n |
COMMON MASKS
| Mask | Hex | Purpose |
|---|---|---|
| Lower 4 bits (nibble) | 0x0F | Extract low nibble |
| Upper 4 bits | 0xF0 | Extract high nibble |
| Lower byte | 0x00FF | Byte 0 |
| Upper byte (16-bit) | 0xFF00 | Byte 1 |
| Lower 16 bits | 0x0000FFFF | Low word |
| Upper 16 bits | 0xFFFF0000 | High word |
| All ones (32-bit) | 0xFFFFFFFF | Full mask / -1 (signed) |
| Alternating 01... | 0x55555555 | Even bits |
| Alternating 10... | 0xAAAAAAAA | Odd bits |
MASTERY CHECKLIST
- Can convert between binary, hex, and decimal in both directions
- Can represent negative numbers using two's complement (flip + add 1)
- Can apply all 4 core mask operations: SET, CLEAR, TOGGLE, CHECK
- Can build a multi-bit mask for any width/position using
((1<<w)-1)<<p - Can insert a bit pattern using 3-step clear-then-OR
- Can extract a bitfield using shift-then-mask
- Can explain why
x & (x-1)detects powers of two - Can explain why
x & (-x)isolates the lowest set bit - Can solve Single Number (LeetCode 136) using XOR in one pass
- Can solve Count Bits (LeetCode 338) using DP in O(n)
- Can write rotate-left / rotate-right for 32-bit unsigned
- Can explain the difference between arithmetic and logical right shift and when each applies
SHIFT-BASED MULTIPLY AND DIVIDE
Shifts are cheaper than multiply/divide on many architectures
Left shift by n = multiply by 2n. Right shift by n = divide by 2n (integer, rounds toward zero for unsigned). Compilers do this automatically, but recognising the pattern matters for bitfield arithmetic.MEMORY ALIGNMENT — THE MOST IMPORTANT TRICK IN SYSTEMS
Why alignment matters
Cache lines (64 bytes), DPDK buffers (128 bytes), DMA descriptors, SIMD vectors — all require naturally aligned addresses. Mis-aligned access causes bus errors on strict architectures and performance penalties everywhere else.Round DOWN to alignment A: aligned = ptr & ~(A-1) Round UP to alignment A: aligned = (ptr + A - 1) & ~(A-1)
Example: A=64, ptr=0x1003 Round down: 0x1003 & ~63 = 0x1003 & 0xFFFFFFC0 = 0x10C0 Round up: (0x1003 + 63) & ~63 = 0x1042 & 0xFFFFFFC0 = 0x1040
// Round address up to next multiple of A uintptr_t alignUp(uintptr_t ptr, size_t A) { return (ptr + A - 1) & ~(A - 1); }
// Round address down uintptr_t alignDown(uintptr_t ptr, size_t A) { return ptr & ~(A - 1); }
// Verify at runtime: assert(((uintptr_t)buf & 63) == 0); // must be 64-byte aligned
MODULO BY POWER OF TWO
x % (2^n) == x & (2^n - 1) — when x is unsigned
This is why ring buffers, hash tables, and DPDK use power-of-2 sizes: the wrapping step becomes a single AND instead of a costly division.// Ring buffer wrap (DPDK-style): uint32_t mask = size - 1; // pre-computed once at init head = (head + 1) & mask; // wrap head cheaply tail = (tail + 1) & mask; // wrap tail cheaply // HASH bucket index (power-of-2 table size): uint32_t bucket = hash(key) & (TABLE_SIZE - 1);
x & (n-1) gives wrong results if n is not a power of two. Always validate with assert((n & (n-1)) == 0) at initialisation.POPULATION COUNT (HAMMING WEIGHT)
Count the number of 1-bits in an integer
Used in checksums, error correction, set cardinality checks, and cryptography. Hardware instruction on modern CPUs; software fallback uses the divide-and-conquer approach.// Parallel popcount — O(log bits), no loop uint32_t popcount32(uint32_t x) { x = x - ((x >> 1) & 0x55555555); // count pairs x = (x & 0x33333333) + ((x >> 2) & 0x33333333); // count nibbles x = (x + (x >> 4)) & 0x0F0F0F0F; // count bytes return (x * 0x01010101) >> 24; // sum all byte counts }
BITMASK DP — SUBSETS AND STATE COMPRESSION
When to use Bitmask DP
Bitmask DP represents state as a bitmask where each bit = one element in-or-out. Applicable when N ≤ 20 (so 2N states fit in memory). Common problems: TSP, assignment, task scheduling, game states.Transitions: For each mask, try adding element i (not yet in mask): newmask = mask | (1 << i) dp[newmask] = min/max(dp[newmask], dp[mask] + cost(i))
Base case: dp[0] = 0 Answer: dp[(1<<N)-1] (full set)
SWAR — SIMD WITHIN A REGISTER
Process multiple values simultaneously with integer arithmetic
SWAR packs multiple sub-word values into a single 64-bit register and uses carefully chosen masks to prevent carry-propagation between adjacent logical fields. Enables 8 parallel byte operations with a single integer add.64-bit word: [B7][B6][B5][B4][B3][B2][B1][B0] (8 bytes)
Naive: requires 8 separate additions SWAR: use overflow-guard mask to prevent carry from byte N into byte N+1
Mask 0x7F7F7F7F7F7F7F7F: Clear the MSB of every byte (guard bit) Allows carry within each byte but blocks inter-byte carry propagation
// Population count via SWAR (classic Hacker’s Delight) uint64_t popcount64_swar(uint64_t x) { x = x - ((x >> 1) & 0x5555555555555555ULL); x = (x & 0x3333333333333333ULL) + ((x >> 2) & 0x3333333333333333ULL); x = (x + (x >> 4)) & 0x0F0F0F0F0F0F0F0FULL; return (x * 0x0101010101010101ULL) >> 56; }