Bitwise operators
Read, set, clear, and flip single bits of a number with &, |, ^, ~, and shifts, keeping in mind that JavaScript does this on 32-bit integers.
O(1)Space O(1)10 practice problemsStep-through pictureThe idea
Computers store whole numbers as bits, a row of 0s and 1s. 13 is 1101 in binary: 8 + 4 + 1. Each position is a bit, numbered from the right starting at 0, so bit 0 is worth 1, bit 1 is worth 2, bit 2 is worth 4, and so on.
Bitwise operators work on every bit position at once. a & b (AND) keeps a 1 only where both have a 1. a | b (OR) keeps a 1 where either has one. a ^ b (XOR) keeps a 1 where exactly one has a 1. ~a (NOT) flips every bit. a << k shifts the bits k places left, which multiplies by 2^k, and a >> k shifts right, which divides by 2^k and rounds down. With a mask like 1 << i, which has only bit i set, these let you test, set, clear, or flip bit i.
JavaScript has a twist. Numbers are normally 64-bit floats, but every bitwise operator first converts its inputs to 32-bit signed integers, works on those, and returns a 32-bit signed result. Bit 31 is the sign bit, so 1 << 31 is -2147483648, and anything above 2^32 loses its high bits. The unsigned right shift >>> is the one exception: it returns an unsigned 32-bit value, so x >>> 0 turns a negative result back into its unsigned value, from 0 to 4294967295.
An everyday example
A row of light switches
Picture a panel of light switches, each either on or off, labeled 0, 1, 2, and so on from the right. To check one switch, cover all the others with tape and see if any light is on. To turn one on, flip it up whatever it was. To turn one off, flip it down. To toggle it, flip it the other way. A mask is the tape stencil that leaves only the switch you care about uncovered.
| In the example | In the code |
|---|---|
The stencil that uncovers only switch i | 1 << i |
Checking if switch i is on | (n >> i) & 1 |
Turning switch i on | n | (1 << i) |
Turning switch i off | n & ~(1 << i) |
Flipping switch i | n ^ (1 << i) |
From the slow way to bitwise operators
- Start with the obvious way to read bit
i: turn the number into text withn.toString(2), counticharacters from the right, and compare with"1". To set the bit you'd edit the text and turn it back into a number. That's slow and clumsy, and for negative numberstoString(2)prints"-101100", not the bits the computer really stores. - Bitwise operators skip the text. They treat a number as a row of 32 bits and work on every position at the same time, in one step. To aim at a single position, build a mask:
1 << iis a number with only bitiset, like16(10000) fori = 4. - Each job then matches one operator. AND with a 0 bit forces 0 and AND with a 1 bit keeps what was there; OR with a 1 forces 1; XOR with a 1 flips. So
n | masksets biti,n & ~maskclears it, andn ^ maskflips it, while every other bit is untouched. To read it,(n >> i) & 1slides bitidown to position 0 and throws the rest away. - Last comes JavaScript's rule: inputs are cut to 32-bit signed integers, negatives are stored in two's complement, and
>>> 0reads the same bits as an unsigned number. One more classic,n & -n, keeps only the lowest set bit. Put each of these on its own line and you getbitTools, the toolkit of bitwise operators.
How it works
- Build a mask for bit
i:1 << ihas only bitiset. - Test bit
i: shift it down to position 0 and keep only that bit,(n >> i) & 1. The result is1or0. - Set bit
iwithn | mask, clear it withn & ~mask, and flip it withn ^ mask. - Isolate the lowest set bit with
n & -n. In two's complement,-nis~n + 1, which shares only that bit withn. - When a result may have bit 31 set, use
>>> 0to read it as an unsigned number.
Watch it run
This picture runs the code below on a real input. Press Step to move one step at a time, or Play to watch. The highlighted line is the one that just ran. Change the input to see other cases.
See every step at onceThe same run, laid out side by side
The code
Point at a note to light up the lines it explains.
function bitTools(n, i) {const isSet = (n >> i) & 1;const withBit = n | (1 << i);const withoutBit = n & ~(1 << i);const flipped = n ^ (1 << i);const lowestBit = n & -n;const unsigned = n >>> 0;return { isSet, withBit, withoutBit, flipped, lowestBit, unsigned };}
How fast it is
| Time | O(1) |
|---|---|
| Extra memory | O(1) |
Each operator works on all 32 bits at once in a single machine step.
- Best case
O(1)Any input: each operator handles all 32 bits in one machine step.
- Average case
O(1)The same six operations for every
nandi. - Worst case
O(1)Still six operations, even for
n = 2147483647ori = 31. Converting the input to 32 bits first is also a fixed amount of work.
Common mistakes
- Forgetting that
1 << 31is negative in JavaScript. Use>>> 0or compare bits instead of comparing with> 0. - Using
>>on a value that might be negative and expecting zeros to come in from the left.>>copies the sign bit;>>>brings in zeros. - Using bitwise operators on numbers above
2^32, such as timestamps in milliseconds. The high bits are silently lost. - Writing
n & 1 === 0.===runs before&, so this isn & (1 === 0). Add brackets:(n & 1) === 0.
When to use it
Look for these clues in a problem:
- The problem talks about binary, bits, flags, masks, or powers of two.
- Values are small sets (up to 32 items) that could each be on or off.
- The statement mentions 32-bit integers, overflow, or unsigned values.
- You need a trick like "is this a power of two" or "lowest set bit" in
O(1).
Check yourself
Answer each one in your head first, then open it.
Why is -44 >> 4 equal to -3 and not a large positive number?
>> copies the sign bit into the places it opens on the left, so the result stays negative. -44 >>> 4 brings in zeros instead and gives 268435453.
What is 44 & -44, and why?
4. The lowest 1 bit of 44 (101100) is worth 4, and it's the only bit that 44 and -44 have in common.
What is 1 << 32 in JavaScript?
1. Shift amounts use only their lowest 5 bits, so 32 becomes 0, and 1 << 0 is 1.
Why does n & 1 === 0 give the wrong answer?
=== runs before &, so it means n & (1 === 0), which is n & false, always 0. Write (n & 1) === 0.
Practice problems
Stuck on one? Tuto is in the playground too, and gives you hints, never the answer.
0 of 10 solved. Solve one to earn the bronze medal, half for silver, and all of them for gold.
Start practicingmedium
Go further
You don't need these to start practicing. Open one when you want more.
Why it works
The reasoning that makes it always right
AND, OR, XOR, and NOT work on each bit position separately: bit k of the result depends only on bit k of the inputs. So you can check any of these lines one position at a time. The mask 1 << i has a 1 at position i and 0 everywhere else.
At every position other than i, the mask bit is 0, and x | 0 = x, x ^ 0 = x, and x & 1 = x (since ~mask has a 1 there). So those bits never change. At position i, x | 1 = 1 sets it, x & 0 = 0 clears it, and x ^ 1 flips it. That's exactly the set, clear, and flip jobs, with nothing else touched. For reading, n >> i moves bit i to position 0, and & 1 keeps only that position.
For n & -n, use the rule -n = ~n + 1. ~n flips every bit. Adding 1 turns the run of 1s at the bottom of ~n (the 0s at the bottom of n) back into 0s and carries into the first 0 of ~n, which is the lowest 1 of n. Above that bit, -n is still the flip of n, so they share nothing there. So the lowest set bit is the only one they share: for 44 (101100), that's 100, which is 4.
Going deeper
3 more ideas
How JavaScript stores the bits
Before any bitwise operator runs, JavaScript converts each input to a 32-bit signed integer. Negative numbers use two's complement: -1 is 32 ones, and -2 is 31 ones followed by a 0. So ~5 is -6, because flipping every bit of 5 gives the two's complement form of -6. Results come back as signed numbers, except for >>>, which treats the bits as unsigned.
Why n & (n - 1) and n & -n work
Subtracting 1 turns the lowest 1 bit into 0 and every 0 below it into 1. So n & (n - 1) clears the lowest set bit, and it is 0 exactly when n had a single 1 bit, which means n is a power of two. -n is ~n + 1: flipping every bit and adding 1 makes the lowest set bit of n the only bit that n and -n share.
Limits to remember
Shifts only use the lowest 5 bits of the shift amount, so 1 << 32 is 1, not 2^32. Numbers above 2^31 - 1 wrap around: 2 ** 31 | 0 is -2147483648. For values beyond 32 bits, use BigInt, which has its own bitwise operators without this limit, such as 1n << 40n.
Work it by hand
Every step in a table, with why
Run bitTools with i = 4 on 44 and on -44, side by side. Bit 4 is off in one and on in the other.
bitTools(44, 4) and bitTools(-44, 4)
| Step | Line | n = 44 | n = -44 | What happens | Why |
|---|---|---|---|---|---|
| 1 | n as 32 bits | 00101100 | …11010100 | the stored bits | 44 is 32 + 8 + 4. -44 is stored in two's complement: flip every bit of 44, then add 1. Only the last 8 of its 32 bits are shown; the rest are all 1. |
| 2 | mask 1 << 4 | 16 (00010000) | 16 (00010000) | only bit 4 is set | The mask is the same for both numbers. |
| 3 | isSet = (n >> 4) & 1 | 2 & 1 = 0 | -3 & 1 = 1 | read bit 4 | 44 >> 4 is 2 (10), whose last bit is 0. -44 >> 4 is -3, because >> copies the sign bit in from the left, and its last bit is 1. |
| 4 | withBit = n | mask | 60 (00111100) | -44 (unchanged) | set bit 4 | For 44 the bit turns on, adding 16. For -44 it was already on, so OR changes nothing. |
| 5 | withoutBit = n & ~mask | 44 (unchanged) | -60 (…11000100) | clear bit 4 | ~16 has every bit set except bit 4. ANDing with it clears bit 4 and keeps the rest. |
| 6 | flipped = n ^ mask | 60 | -60 | flip bit 4 | XOR flips the bit either way: on for 44, off for -44. |
| 7 | lowestBit = n & -n | 4 (00000100) | 4 (00000100) | lowest set bit | 44 and -44 share exactly one bit: the lowest 1 of 44, worth 4. |
| 8 | unsigned = n >>> 0 | 44 | 4294967252 | read as unsigned | For -44, the same 32 bits read without a sign bit give 2^32 - 44 = 4294967252. |
For 44 the result is { isSet: 0, withBit: 60, withoutBit: 44, flipped: 60, lowestBit: 4, unsigned: 44 }. For -44 it is { isSet: 1, withBit: -44, withoutBit: -60, flipped: -60, lowestBit: 4, unsigned: 4294967252 }. Every line is one operation, so there's no loop to end.
Compared with other ways
When to pick something else
| Approach | Time | Extra memory | Choose it when |
|---|---|---|---|
| Bitwise operators | O(1) | O(1) | Reading or changing individual bits of values that fit in 32 bits: flags, masks, packed colors. |
Binary text with toString(2) | O(w) | O(w) | Only for showing bits to a person while debugging. Use (n >>> 0).toString(2) so negatives show their real bits. |
| An array of booleans | O(1) | O(n) | More than 31 on/off values, or when readable code matters more than compact storage. |
BigInt bitwise operators | O(n) | O(n) | Values wider than 32 bits, such as 64-bit IDs. Each operation costs more as the number grows. |
Extra memory: O(1): six numbers in the returned object.
How the steps grow
- Bitwise operators, An array of booleans
O(1) - Binary text with
toString(2),BigIntbitwise operatorsO(w)
| Approach | n = 10 | n = 1,000 | n = 1,000,000 |
|---|---|---|---|
| Bitwise operators | 1 | 1 | 1 |
Binary text with toString(2) | 10 | 1,000 | 1 million |
| An array of booleans | 1 | 1 | 1 |
BigInt bitwise operators | 10 | 1,000 | 1 million |
Inputs that need care
6 tricky inputs
- Bit 31:
bitTools(5, 31) 1 << 31is-2147483648, sowithBitcomes out as-2147483643, a negative number. Read it with>>> 0to get2147483653, and never test bit 31 with> 0.- A shift of 32 or more:
bitTools(5, 32) - Shifts only use the lowest 5 bits of the amount, so
i = 32acts likei = 0:isSetis 1 andwithoutBitis 4. Check thatiis from 0 to 31. - A number above
2^32:bitTools(2 ** 32 + 6, 1) - The high bits are dropped before any operator runs, so it behaves exactly like
n = 6. Timestamps in milliseconds are this large; useBigIntfor them. n = 0lowestBitis 0, which is how you know there is no set bit at all.isSetis 0 for everyi.- The smallest 32-bit number:
-2147483648 -ndoesn't fit in 32 bits and wraps back to itself, solowestBitis-2147483648, andunsignedis2147483648. It's the one value whose negative is itself.- A decimal:
bitTools(5.7, 0) - Bitwise operators cut off the fraction first, so it behaves like
n = 5. That's whyx | 0is sometimes used to round toward zero, but only for values that fit in 32 bits.
Variations and follow-ups
Twists interviewers add
Packing several values into one number
A color like 0xff8800 packs red, green, and blue into 8 bits each. Read green with (color >> 8) & 255, which gives 136. Build it with (r << 16) | (g << 8) | b. Reading a field is shift, then mask.
Power-of-two test
n > 0 && (n & (n - 1)) === 0 is true only when n has a single 1 bit. Subtracting 1 clears that bit and sets the ones below it, so the AND is 0. It's used to check sizes for buffers and textures.
BigInt bits
BigInt has its own &, |, ^, ~, <<, and >> with no 32-bit limit, so 1n << 40n is exact. There's no >>>: 5n >>> 0n throws a TypeError, because BigInt values have no fixed width to be unsigned in.
Built-in helpers
Math.clz32(x) counts the leading zero bits of x as a 32-bit number, so 31 - Math.clz32(x) is the position of its highest set bit. Uint32Array stores values as unsigned 32-bit numbers, which avoids the sign surprise.
In an interview
Spot it, explain it, answer follow-ups
- Spot it from "without using
+", "check if a number is a power of two", "toggle the state", "the lowest set bit", or a hint that values fit in 32 bits. - Explain each operation as "build a mask with only bit
i, then AND to read or clear, OR to set, XOR to flip". Saying the mask first makes every line easy to follow. - Test a negative number, bit 0, and bit 31. Bit 31 is where JavaScript's signed results surprise people, and
>>versus>>>behaves differently there. - Expect "why does
n & -ngive the lowest set bit?". Answer with-n = ~n + 1: the+ 1carries up to exactly the lowest 1 ofn, and above it-nandndisagree on every bit.
Where frontend code uses it
The same idea in real UI code
- WebGL takes bit masks as arguments:
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)clears two buffers in one call. x | 0is an old trick to cut a number to a 32-bit integer, and you'll see it in minified code and in asm.js-style code.Math.truncis clearer for normal use.- Canvas pixel data is often read through a
Uint32Arrayview of anImageDatabuffer, where each pixel's four color bytes are packed into one 32-bit number and pulled apart with shifts and& 0xff.