Loading content...
Before you can manipulate individual bits, you must first learn to see them. Checking whether a specific bit is set or clear is the most fundamental operation in bit manipulation—the equivalent of reading before writing, of observation before action.
Every advanced bit manipulation technique builds upon this foundation. Whether you're implementing permission systems, decoding protocol headers, optimizing set operations, or debugging low-level hardware interactions, the ability to query individual bit states is indispensable. This operation appears so frequently in systems programming that it becomes second nature to experienced engineers.
In this page, we'll develop a deep, intuitive understanding of how to check any bit at any position—with full clarity on the underlying mechanics, edge cases, and real-world applications.
By the end of this page, you will understand: (1) The fundamental formula for checking a bit at position i, (2) Why left-shifting 1 creates the perfect bit mask, (3) How the AND operation isolates exactly the bit we want, (4) The difference between returning boolean vs. returning the bit value, (5) Zero-indexed bit positions and their relationship to powers of two, and (6) Real-world applications in permission systems, flags, and protocol parsing.
Let's formalize exactly what we're trying to accomplish:
Given:
n (our target number)i (the bit position we want to inspect)Goal:
i in n is set (1) or clear (0)Constraints and Conventions:
Let's visualize this with a concrete example. Consider the number 42 in 8-bit representation:
Number: 42Binary: 0 0 1 0 1 0 1 0 ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑Pos: 7 6 5 4 3 2 1 0 Bit at position 0: 0 (clear)Bit at position 1: 1 (set)Bit at position 2: 0 (clear)Bit at position 3: 1 (set)Bit at position 4: 0 (clear)Bit at position 5: 1 (set)Bit at position 6: 0 (clear)Bit at position 7: 0 (clear) Verification: 2¹ + 2³ + 2⁵ = 2 + 8 + 32 = 42 ✓The question we must answer efficiently: How can we extract information about just one of these bit positions without affecting or even needing to know the others?
The naive approach would be to convert the number to a binary string, index into it, and read the character. But this is:
The elegant solution uses bitwise operations to query the exact bit we want in O(1) time with zero memory allocation.
There are two equivalent formulas for checking the bit at position i. Both are widely used, and understanding both gives you flexibility depending on context:
Formula 1: Shift the number right, then AND with 1
(n >> i) & 1
Formula 2: Create a mask by shifting 1 left, then AND with n
n & (1 << i)
These formulas produce slightly different results:
Both tell you whether the bit is set, but Formula 1 normalizes the result to a boolean-like 0/1, while Formula 2 returns the "weighted" value of that bit position.
Let's dissect each formula in detail.
Use (n >> i) & 1 when you need a clean 0 or 1 result (e.g., for boolean conditions, indexing arrays by bit values, or accumulating bit counts). Use n & (1 << i) when you only need to know if the bit is set and the actual value doesn't matter—the result is truthy (non-zero) if set, falsy (zero) if clear. In practice, both are equally fast and compile to similar machine code.
Let's trace through Formula 1 step by step with a concrete example.
Problem: Check if bit 3 is set in the number 42.
Step 1: Start with n = 42
n = 42Binary: 0 0 1 0 1 0 1 0 7 6 5 4 3 2 1 0 ← bit positions We want to check bit at position 3 (which has value 1)Step 2: Right-shift n by i positions (n >> 3)
Right-shifting by 3 moves all bits 3 positions to the right. The rightmost 3 bits fall off, and zeros fill in from the left:
Original: 0 0 1 0 1 0 1 0 (42) ↓ ↓ ↓ ↓ ↓After >> 3: 0 0 0 0 0 1 0 1 (5) The bit that was at position 3 is now at position 0!The rightmost 3 bits (0, 1, 0) were discarded.Step 3: AND with 1 to isolate the final bit ((n >> 3) & 1)
Now we AND the shifted result with 1. Since 1 in binary is 00000001, this masks out everything except the least significant bit:
n >> 3: 0 0 0 0 0 1 0 1 (5) & 0 0 0 0 0 0 0 1 (1) ─────────────────────Result: 0 0 0 0 0 0 0 1 (1) The bit at position 3 was 1, so the result is 1.If the bit had been 0, the result would be 0.Right-shifting brings our target bit to position 0 (the LSB). ANDing with 1 then isolates just that bit, giving us a clean 0 or 1 answer. It's like sliding a window over the number and then looking through a pinhole that shows only one bit.
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889
// Formula 1: (n >> i) & 1// Returns 0 if bit is clear, 1 if bit is set function checkBit(n: number, i: number): number { return (n >> i) & 1;} function isBitSet(n: number, i: number): boolean { return ((n >> i) & 1) === 1;} // Examplesconsole.log(checkBit(42, 0)); // 0 (bit 0 is clear)console.log(checkBit(42, 1)); // 1 (bit 1 is set)console.log(checkBit(42, 3)); // 1 (bit 3 is set)console.log(checkBit(42, 4)); // 0 (bit 4 is clear) console.log(isBitSet(42, 3)); // trueconsole.log(isBitSet(42, 4)); // false ---python# Formula 1: (n >> i) & 1# Returns 0 if bit is clear, 1 if bit is set def check_bit(n: int, i: int) -> int: return (n >> i) & 1 def is_bit_set(n: int, i: int) -> bool: return ((n >> i) & 1) == 1 # Examplesprint(check_bit(42, 0)) # 0 (bit 0 is clear)print(check_bit(42, 1)) # 1 (bit 1 is set)print(check_bit(42, 3)) # 1 (bit 3 is set)print(check_bit(42, 4)) # 0 (bit 4 is clear) print(is_bit_set(42, 3)) # Trueprint(is_bit_set(42, 4)) # False ---javapublic class BitCheck { // Formula 1: (n >> i) & 1 // Returns 0 if bit is clear, 1 if bit is set public static int checkBit(int n, int i) { return (n >> i) & 1; } public static boolean isBitSet(int n, int i) { return ((n >> i) & 1) == 1; } public static void main(String[] args) { System.out.println(checkBit(42, 0)); // 0 (bit 0 is clear) System.out.println(checkBit(42, 1)); // 1 (bit 1 is set) System.out.println(checkBit(42, 3)); // 1 (bit 3 is set) System.out.println(checkBit(42, 4)); // 0 (bit 4 is clear) System.out.println(isBitSet(42, 3)); // true System.out.println(isBitSet(42, 4)); // false }} ---cpp#include <iostream> // Formula 1: (n >> i) & 1// Returns 0 if bit is clear, 1 if bit is set int checkBit(int n, int i) { return (n >> i) & 1;} bool isBitSet(int n, int i) { return ((n >> i) & 1) == 1;} int main() { std::cout << checkBit(42, 0) << std::endl; // 0 (bit 0 is clear) std::cout << checkBit(42, 1) << std::endl; // 1 (bit 1 is set) std::cout << checkBit(42, 3) << std::endl; // 1 (bit 3 is set) std::cout << checkBit(42, 4) << std::endl; // 0 (bit 4 is clear) std::cout << std::boolalpha; std::cout << isBitSet(42, 3) << std::endl; // true std::cout << isBitSet(42, 4) << std::endl; // false return 0;}Formula 2 takes the opposite approach: instead of moving the bit to position 0, we create a mask that has a 1 only at position i, then AND that mask with the original number.
Problem: Check if bit 3 is set in the number 42.
Step 1: Create the mask by shifting 1 left by i positions (1 << 3)
Original 1: 0 0 0 0 0 0 0 1 ↓ ↓ ↓After << 3: 0 0 0 0 1 0 0 0 (8 = 2³) This mask has exactly one bit set: at position 3.We call this a "single-bit mask" or "positional mask".Step 2: AND the original number with the mask (42 & 8)
n = 42: 0 0 1 0 1 0 1 0mask = 8: 0 0 0 0 1 0 0 0 & ─────────────────Result: 0 0 0 0 1 0 0 0 (8) Since bit 3 is set in n, the result equals the mask (8 = 2³).If bit 3 were clear, the result would be 0.Comparing the results:
| Formula | Result when bit is SET | Result when bit is CLEAR |
|---|---|---|
(n >> i) & 1 | 1 | 0 |
n & (1 << i) | 2^i (positional value) | 0 |
Both formulas give zero when the bit is clear, so they both work as boolean conditions. But Formula 2's non-zero result varies by position.
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495
// Formula 2: n & (1 << i)// Returns 0 if bit is clear, 2^i if bit is set function checkBitMask(n: number, i: number): number { return n & (1 << i);} function isBitSetMask(n: number, i: number): boolean { return (n & (1 << i)) !== 0;} // Examples - note the different non-zero valuesconsole.log(checkBitMask(42, 1)); // 2 (bit 1 is set, 2^1 = 2)console.log(checkBitMask(42, 3)); // 8 (bit 3 is set, 2^3 = 8)console.log(checkBitMask(42, 5)); // 32 (bit 5 is set, 2^5 = 32)console.log(checkBitMask(42, 4)); // 0 (bit 4 is clear) // Both work identically as boolean conditionsconsole.log(isBitSetMask(42, 3)); // trueconsole.log(isBitSetMask(42, 4)); // false ---python# Formula 2: n & (1 << i)# Returns 0 if bit is clear, 2^i if bit is set def check_bit_mask(n: int, i: int) -> int: return n & (1 << i) def is_bit_set_mask(n: int, i: int) -> bool: return (n & (1 << i)) != 0 # Examples - note the different non-zero valuesprint(check_bit_mask(42, 1)) # 2 (bit 1 is set, 2^1 = 2)print(check_bit_mask(42, 3)) # 8 (bit 3 is set, 2^3 = 8)print(check_bit_mask(42, 5)) # 32 (bit 5 is set, 2^5 = 32)print(check_bit_mask(42, 4)) # 0 (bit 4 is clear) # Both work identically as boolean conditionsprint(is_bit_set_mask(42, 3)) # Trueprint(is_bit_set_mask(42, 4)) # False ---javapublic class BitCheckMask { // Formula 2: n & (1 << i) // Returns 0 if bit is clear, 2^i if bit is set public static int checkBitMask(int n, int i) { return n & (1 << i); } public static boolean isBitSetMask(int n, int i) { return (n & (1 << i)) != 0; } public static void main(String[] args) { // Examples - note the different non-zero values System.out.println(checkBitMask(42, 1)); // 2 (bit 1 is set) System.out.println(checkBitMask(42, 3)); // 8 (bit 3 is set) System.out.println(checkBitMask(42, 5)); // 32 (bit 5 is set) System.out.println(checkBitMask(42, 4)); // 0 (bit 4 is clear) // Both work identically as boolean conditions System.out.println(isBitSetMask(42, 3)); // true System.out.println(isBitSetMask(42, 4)); // false }} ---cpp#include <iostream> // Formula 2: n & (1 << i)// Returns 0 if bit is clear, 2^i if bit is set int checkBitMask(int n, int i) { return n & (1 << i);} bool isBitSetMask(int n, int i) { return (n & (1 << i)) != 0;} int main() { // Examples - note the different non-zero values std::cout << checkBitMask(42, 1) << std::endl; // 2 (bit 1 is set) std::cout << checkBitMask(42, 3) << std::endl; // 8 (bit 3 is set) std::cout << checkBitMask(42, 5) << std::endl; // 32 (bit 5 is set) std::cout << checkBitMask(42, 4) << std::endl; // 0 (bit 4 is clear) std::cout << std::boolalpha; // Both work identically as boolean conditions std::cout << isBitSetMask(42, 3) << std::endl; // true std::cout << isBitSetMask(42, 4) << std::endl; // false return 0;}When using Formula 2, never compare the result directly to 1. The expression (n & (1 << i)) == 1 is WRONG for any position except i=0. Always compare to 0 (for falsy) or use != 0 (for truthy). Alternatively, use Formula 1 if you need the result to be exactly 0 or 1.
A deep understanding of bit positions is essential for error-free bit manipulation. Let's cement this concept with a comprehensive reference.
The Position-Value Relationship:
Each bit position has an associated positional value equal to 2^position. When a bit is set at position i, it contributes 2^i to the number's total value:
| Position | Value (2^i) | Name/Role |
|---|---|---|
| 0 | 1 | LSB (Least Significant Bit) |
| 1 | 2 | |
| 2 | 4 | |
| 3 | 8 | |
| 4 | 16 | |
| 5 | 32 | |
| 6 | 64 | |
| 7 | 128 | MSB (for 8-bit) |
| 15 | 32,768 | MSB (for 16-bit signed) |
| 31 | 2,147,483,648 | MSB (for 32-bit) |
| 63 | 9.2 × 10^18 | MSB (for 64-bit) |
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273
// Demonstrating positional values and checking bits across the range function explainBits(n: number, numBits: number = 8): void { console.log(`\nNumber: ${n}`); console.log("Position | Value | Bit | Contributes"); console.log("---------|-------|-----|------------"); for (let i = numBits - 1; i >= 0; i--) { const bitValue = (n >> i) & 1; const positionalValue = 1 << i; const contributes = bitValue === 1 ? positionalValue : 0; console.log( ` ${i.toString().padStart(2)} | ${positionalValue.toString().padStart(3)} | ${bitValue} | ${contributes}` ); } // Verify: sum of contributions equals n let sum = 0; for (let i = 0; i < numBits; i++) { if ((n >> i) & 1) { sum += (1 << i); } } console.log(`\nVerification: sum of set bit values = ${sum}`);} explainBits(42);// Number: 42// Position | Value | Bit | Contributes// ---------|-------|-----|------------// 7 | 128 | 0 | 0// 6 | 64 | 0 | 0// 5 | 32 | 1 | 32// 4 | 16 | 0 | 0// 3 | 8 | 1 | 8// 2 | 4 | 0 | 0// 1 | 2 | 1 | 2// 0 | 1 | 0 | 0// Verification: sum of set bit values = 42 ---python# Demonstrating positional values and checking bits across the range def explain_bits(n: int, num_bits: int = 8) -> None: print(f"\nNumber: {n}") print("Position | Value | Bit | Contributes") print("---------|-------|-----|------------") for i in range(num_bits - 1, -1, -1): bit_value = (n >> i) & 1 positional_value = 1 << i contributes = positional_value if bit_value == 1 else 0 print(f" {i:2} | {positional_value:3} | {bit_value} | {contributes}") # Verify: sum of contributions equals n total = sum((1 << i) for i in range(num_bits) if (n >> i) & 1) print(f"\nVerification: sum of set bit values = {total}") explain_bits(42)# Number: 42# Position | Value | Bit | Contributes# ---------|-------|-----|------------# 7 | 128 | 0 | 0# 6 | 64 | 0 | 0# 5 | 32 | 1 | 32# 4 | 16 | 0 | 0# 3 | 8 | 1 | 8# 2 | 4 | 0 | 0# 1 | 2 | 1 | 2# 0 | 1 | 0 | 0# Verification: sum of set bit values = 42Think of any integer as a sum of powers of two, where each bit position 'votes' on whether its power of two is included. Checking a bit is asking: 'Does this position vote yes (1) or no (0)?'
Bit operations are low-level and unforgiving. Let's examine the edge cases and common pitfalls you need to handle correctly.
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071
// Edge case demonstrations // 1. Checking bits in negative numbers (two's complement)const negOne = -1;console.log("Bits of -1:");for (let i = 31; i >= 0; i--) { process.stdout.write(String((negOne >> i) & 1));}console.log(); // 11111111111111111111111111111111 (all 1s!) // 2. Checking the sign bit (bit 31 in 32-bit)function isNegative(n: number): boolean { // For 32-bit integers, bit 31 is the sign bit return ((n >> 31) & 1) === 1;}console.log(isNegative(42)); // falseconsole.log(isNegative(-42)); // true // 3. Safe bit check with bounds validationfunction safeBitCheck(n: number, i: number): number | null { if (i < 0 || i > 31) { console.warn(`Bit position ${i} out of valid range [0, 31]`); return null; } return (n >> i) & 1;}console.log(safeBitCheck(42, 3)); // 1console.log(safeBitCheck(42, 50)); // null (with warning) // 4. Zero - all positions return 0const zero = 0;console.log(`Bit 0 of 0: ${(zero >> 0) & 1}`); // 0console.log(`Bit 15 of 0: ${(zero >> 15) & 1}`); // 0 ---python# Edge case demonstrations # 1. Checking bits in negative numbers (two's complement)# Python integers have arbitrary precision, so we need to mask for 32-bitneg_one = -1 & 0xFFFFFFFF # Simulate 32-bitprint("Bits of -1 (32-bit):")print(''.join(str((neg_one >> i) & 1) for i in range(31, -1, -1)))# 11111111111111111111111111111111 # 2. Checking the sign bit (bit 31 in 32-bit representation)def is_negative_32bit(n: int) -> bool: """Check if a 32-bit integer is negative""" # Mask to 32 bits first, then check sign bit masked = n & 0xFFFFFFFF return ((masked >> 31) & 1) == 1 print(is_negative_32bit(42)) # Falseprint(is_negative_32bit(-42)) # True # 3. Safe bit check with bounds validationdef safe_bit_check(n: int, i: int, bit_width: int = 32) -> int | None: if i < 0 or i >= bit_width: print(f"Warning: Bit position {i} out of valid range [0, {bit_width-1}]") return None # Mask to ensure consistent behavior with negative numbers masked = n & ((1 << bit_width) - 1) return (masked >> i) & 1 print(safe_bit_check(42, 3)) # 1print(safe_bit_check(42, 50)) # None (with warning) # 4. Zero - all positions return 0zero = 0print(f"Bit 0 of 0: {(zero >> 0) & 1}") # 0print(f"Bit 15 of 0: {(zero >> 15) & 1}") # 0The ability to check individual bits appears throughout systems programming and algorithm design. Let's explore several practical applications.
Permission systems commonly represent capabilities as bits in an integer. Each bit position corresponds to a specific permission, and checking whether a user has a permission is a bit check.
12345678910111213141516171819202122232425262728293031
// Unix-style file permissions using bitsconst PERM_READ = 0b100; // bit 2 = 4const PERM_WRITE = 0b010; // bit 1 = 2const PERM_EXEC = 0b001; // bit 0 = 1 function hasPermission(userPerms: number, required: number): boolean { return (userPerms & required) === required;} function checkReadPermission(perms: number): boolean { return ((perms >> 2) & 1) === 1; // Check bit 2} function checkWritePermission(perms: number): boolean { return ((perms >> 1) & 1) === 1; // Check bit 1} function checkExecPermission(perms: number): boolean { return (perms & 1) === 1; // Check bit 0} // Example: rwx (read-write-execute) = 7 = 0b111const rwx = 7;console.log(`Read: ${checkReadPermission(rwx)}`); // trueconsole.log(`Write: ${checkWritePermission(rwx)}`); // trueconsole.log(`Exec: ${checkExecPermission(rwx)}`); // true // Example: r-- (read-only) = 4 = 0b100const readOnly = 4;console.log(`Read: ${checkReadPermission(readOnly)}`); // trueconsole.log(`Write: ${checkWritePermission(readOnly)}`); // falseBit operations are among the fastest operations a CPU can perform. Let's understand why and what guarantees you have.
Bit checking is especially valuable in hot paths where you're checking flags millions of times per second (game loops, packet processing, rendering pipelines). The difference between a bit check and a boolean property lookup might be negligible for one call, but across millions of iterations, it compounds significantly.
You've now mastered the first and most fundamental bit manipulation operation: checking a bit at a specific position. Let's consolidate what we've learned.
(n >> i) & 1 returns 0 or 1; n & (1 << i) returns 0 or 2^i. Both work for boolean conditions.What's Next:
Now that you can read bits, it's time to learn to write them. The next page covers setting a bit at position i—turning a 0 into a 1 without affecting any other bits. This is the logical complement to checking: once you can see, you can change.
You now have a deep understanding of how to check whether any bit is set at any position in any integer. This foundational skill underlies all bit manipulation techniques. Next, we'll learn to modify bits by setting them to 1.