Bitwise Operators in a Nutshell
Learn about bitwise operations, bits, and hexidecimal numbers
If you’re already comfortable writing code—conditionals, loops, maybe some data structures—you’ve probably used logical operators like &&, ||, and !, and comparison operators like ==, >, and !=. These map pretty naturally to how we think. But bitwise operators? Those tend to trip people up, even developers with solid fundamentals.
This article covers what bitwise operators are, how binary and hexadecimal numbers work (since you need that foundation first), and where bitwise operations actually show up in real code—including cryptography and performance tricks.
Hexadecimal Numbers and Binary Numbers
To understand bitwise operators, you need to be comfortable with two number systems: binary and hexadecimal. If you already know them, feel free to skip ahead. If not, read this section carefully—it’s the foundation everything else builds on.
Binary Numbers
A computer only works with two states: on and off, represented as 1 and 0. Every number you work with in code is ultimately stored as a sequence of these bits.
The trick is positional value. In decimal (base 10), the number 342 means: 3×100 + 4×10 + 2×1. Binary works the same way, but each position represents a power of 2 instead of 10:
Bit position: 7 6 5 4 3 2 1 0
Power of 2: 128 64 32 16 8 4 2 1
So 1011 in binary is: 1×8 + 0×4 + 1×2 + 1×1 = 11. And 1100 is: 1×8 + 1×4 + 0×2 + 0×1 = 12.
Some examples to get a feel for it:
0→00001→00012→00103→00117→011112→1100
Hexadecimal Numbers
Binary is precise but verbose—large numbers become long strings of bits fast. Hexadecimal (base 16) is a more compact representation that maps cleanly onto binary.
Hex uses 16 digits: 0–9 and a–f, where a=10, b=11, up to f=15. Hex values are typically prefixed with 0x to distinguish them from decimal.
Each hex digit represents exactly 4 bits (a “nibble”). The reason is that 4 bits can hold a maximum value of 1111, which equals 15—the same as 0xf. This makes conversion between hex and binary very straightforward:
0x0→0000,0x8→10000x1→0001,0x9→10010x2→0010,0xa→10100x3→0011,0xb→10110x4→0100,0xc→11000x5→0101,0xd→11010x6→0110,0xe→11100x7→0111,0xf→1111
So 0xAB in binary is 10101011, and as a decimal: (10×16) + 11 = 171.
This is also why HTML colors are written as #ffffff—it’s three hex bytes (red, green, blue), each ranging from 0x00 (0) to 0xff (255). White is full intensity on all three channels.
Overview of Bitwise Operators
Bitwise operators work directly on the binary representation of numbers, bit by bit. There are six of them: AND, OR, XOR, NOT, Left Shift, and Right Shift.
AND (&)
Returns 1 only if both bits are 1.
1011
& 0111
——
0011
Common use: masking. AND a value with a mask that has 1s only in the bit positions you care about to extract those bits.
OR (|)
Returns 1 if at least one bit is 1.
1011
| 0111
——
1111
Common use: setting specific bits. OR a value with a mask to force certain bits to 1 without touching the others.
XOR (^)
Returns 1 if the bits are different.
1011
^ 0111
——
1100
XOR has a critical property: applying it twice with the same value undoes itself. That is, a ^ b ^ b == a. This is the foundation of the Vernam cipher, covered below.
NOT (~)
Inverts every bit—0 becomes 1, 1 becomes 0.
~ 1011
——
0100
In practice, the result depends on your integer type’s bit width. On a 32-bit integer, ~0 gives 0xFFFFFFFF, not just 1.
Left Shift (<<)
Shifts all bits to the left by N positions, filling vacated positions on the right with 0.
101 << 2
= 10100
Each left shift by 1 is equivalent to multiplying by 2. Shifting left by N multiplies by 2^N:
5 << 1 = 10(5 × 2)5 << 2 = 20(5 × 4)5 << 3 = 40(5 × 8)
This is faster than multiplication on many architectures and is commonly used in performance-sensitive and embedded code.
Right Shift (>>)
Shifts all bits to the right by N positions, filling vacated positions on the left with 0 (for unsigned values).
1010 >> 1
= 0101
Each right shift by 1 is equivalent to integer division by 2, discarding the remainder:
20 >> 1 = 10(20 ÷ 2)20 >> 2 = 5(20 ÷ 4)20 >> 3 = 2(20 ÷ 8, remainder dropped)
Like left shift, this is a fast substitute for power-of-two division in low-level code.
Exercises
Work these out by hand—convert to binary first, then apply the operator:
1011 ^ 0111101 << 20xAB ^ 0x57101 | 000- What decimal value does
1 << 7produce?
Answers at the end of the article.
Use Cases of Bitwise Operators
Bitwise operators are most powerful when embedded inside larger algorithms. Here are the two most common real-world patterns.
Cryptography: The Vernam Cipher
The Vernam cipher (one-time pad) is the only theoretically unbreakable encryption scheme—and it’s built entirely on XOR.
The idea: take your message and XOR it byte-by-byte with a key of the same length. To decrypt, XOR the ciphertext with the same key again. This works because of XOR’s self-inverse property: a ^ b ^ b == a.
For example, the message "hello" in ASCII hex is:
48 65 6C 6C 6F
And the key "anton" in ASCII hex is:
41 6E 74 6F 6E
XOR each pair:
0x48 ^ 0x41 = 0x09
0x65 ^ 0x6E = 0x0B
0x6C ^ 0x74 = 0x18
0x6C ^ 0x6F = 0x03
0x6F ^ 0x6E = 0x01
That byte sequence is the ciphertext. XOR it again with "anton" and you get "hello" back. You can see this implemented in the ClearCipher repository.
Bit Flags
A very common pattern in systems programming is packing multiple boolean flags into a single integer, with each bit representing one flag:
#define FLAG_READ 0x01 // 0001
#define FLAG_WRITE 0x02 // 0010
#define FLAG_EXECUTE 0x04 // 0100
int permissions = FLAG_READ | FLAG_WRITE; // 0011
// Check if write is set:
if (permissions & FLAG_WRITE) { ... }
// Remove write permission:
permissions &= ~FLAG_WRITE;
This is exactly how Unix file permissions work under the hood.
Exercise Answers
1011 ^ 0111 = 1100(decimal 12)101 << 2 = 10100(decimal 20)0xAB ^ 0x57 = 0xFC(binary:10101011 ^ 01010111 = 11111100)101 | 000 = 101(decimal 5)1 << 7 = 128
Once these operators click, a lot of low-level code that looked cryptic will start making a lot more sense.