A bit is a physical thing
A bit is not the number 0 or the number 1. A bit is something in the world that can be in two clearly different states, and we agreed to call those states 0 and 1.
Why two? Because two is the easiest number of states to build reliably. If you try to build a component that holds ten distinguishable voltage levels, small amounts of heat, interference and manufacturing variation start making level 6 look like level 7. If you only have to tell high from low, you can be wrong by an enormous margin and still get the right answer. Two states is the engineering choice that makes everything else possible.
| Where | State we call 0 | State we call 1 |
|---|---|---|
| RAM | capacitor discharged | capacitor charged |
| CPU register | low voltage (~0 V) | high voltage (~1 V) |
| SSD | no charge trapped in the cell | charge trapped |
| Hard disk | magnetised one way | magnetised the other |
| Network cable | one signal level or phase | the other |
| CD / DVD | flat surface | a pit that scatters the laser |
A refinement, because that table is simpler than reality. Modern SSDs and fast network links cheat. Consumer flash is usually TLC or QLC — 8 or 16 distinguishable charge levels per cell, storing three or four bits at once. Gigabit Ethernet uses five signal levels, 10-gigabit uses sixteen, PCIe 6.0 uses four. Each of them accepts exactly the fragility described above, and pays for it with heavy error correction, because density and bandwidth are worth the trouble.
But watch what happens the moment the signal is recovered: it is converted straight back into plain bits, and everything above that point is two-state again. Two states is not where the physics stops — it is what everything is normalised to. Which is a stronger claim than the one the table makes on its own.
Nothing in that table is a number. Charge, voltage, magnetism, a physical dent in plastic. The number is what we project onto it. When people say a computer "works in ones and zeros," this is what is actually meant: it works in two reliably distinguishable physical conditions, and we find it convenient to write them down as 0 and 1.
One bit answers exactly one yes-or-no question. That's it. To hold anything more interesting you need more of them — and the whole rest of this page is about what happens when you line bits up in a row.
Counting with bits
Binary is not exotic. It is the counting system you already use, with one number changed.
Look at how you read the decimal number 3 0 7. You don't treat those as three separate digits — you read each position as being worth ten times the one to its right:
3 × 100 = 300
0 × 10 = 0
7 × 1 = 7
─────────────
total = 307
Binary does exactly the same thing with two instead of ten. Each position is worth twice the one to its right: 1, 2, 4, 8, 16, 32, 64, 128. And because each digit can only be 0 or 1, you never multiply — you just decide whether to include that place value or not.
1 × 16 = 16 ← include
0 × 8 = 0
0 × 4 = 0
1 × 2 = 2 ← include
1 × 1 = 1 ← include
─────────────
total = 19
Watch it count
The clearest way to feel binary is to watch it tick over. Step this forward and notice something: the rightmost bit flips on every step, the next one every 2 steps, the next every 4, the next every 8. Each column flips half as often as the one to its right.
decimal 0 · hex 0x00
Press +1 and watch which switches change.
Carrying works the same as it does in decimal. When you count 7 → 8 → 9 in decimal, the next step forces a carry: 9 rolls to 0 and the column to its left goes up by one. Binary just runs out of digits much sooner — it carries after 1 instead of after 9. That is why 0111 + 1 becomes 1000: three columns roll over at once.
Eight bits make a byte
One bit gives you two possibilities. Add a second and you get four, because the second bit doubles every option the first one had. Every bit you add doubles the total.
Why 256 stops at 255. Eight bits give you 256 different patterns, and the first one is 00000000 — which we read as zero. So the patterns are numbered 0 through 255, not 1 through 256. This off-by-one is the source of an enormous amount of real-world weirdness, and it is why the largest value a single byte can hold is 255.
You met that limit on the previous page without knowing it. Python's bytecode uses two bytes per instruction: one for the opcode, one for its argument. Because the argument is a single byte, it cannot exceed 255 — so when an instruction needs a bigger number, the compiler emits an extra EXTENDED_ARG instruction in front of it to supply the higher bits. That whole mechanism exists because of the number in the table above.
Why eight in particular?
Partly history — IBM's System/360 settled on it in 1964 and the industry followed. But it stuck because eight is genuinely convenient: it is a power of two (so bytes tile neatly into larger units), it splits evenly in half (two groups of four, which matters enormously in the next section), and 256 values is just enough to cover the Latin alphabet in both cases, the digits, punctuation and a pile of control codes.
Hexadecimal, decoded
Hex looks like a foreign language and is usually taught as one. It isn't. Hex is a compression scheme for writing binary down — and once you see the mechanism, there is essentially nothing left to learn.
Start with the problem. Here is one byte in binary:
11010010
That is miserable to read, worse to say aloud, and very easy to miscount. The obvious fix is to write it in decimal — 210 — but decimal creates a new problem: 10 is not a power of 2, so decimal digits do not line up with bit boundaries. Change one bit and the decimal number changes unpredictably. You cannot look at 210 and see which bits are on.
So pick a base that is a power of two. Sixteen is 2⁴ — which means one hex digit encodes exactly four bits, always. Not approximately. Not usually. Exactly, every time.
The whole trick. Cut the byte down the middle into two groups of four bits. Each group of four has 16 possible patterns (0000 through 1111), so each gets its own single symbol. Two symbols, one byte, no arithmetic required. A group of four bits is called a nibble — half a byte, and yes that is the actual technical term.
0xD2 = 210
Click the switches. Notice that changing a bit on the left never touches the right hex digit — the two halves are completely independent. That is the property decimal cannot give you.
Where the letters come from
A nibble has 16 possible values, and we need 16 distinct symbols to name them. We only have ten digit characters, 0 through 9. So the remaining six borrow the first six letters of the alphabet: A is ten, B is eleven, and so on up to F, which is fifteen. They are not letters in any meaningful sense — they are digits that happen to be shaped like letters, because we ran out of digit shapes.
You do not need to memorise this. Four patterns carry the rest: 8 is 1000, 4 is 0100, 2 is 0010, 1 is 0001. Everything else is those added together — D is 8+4+1, so 1101.
Converting between all three
Type into any of these and the other two follow. The bottom row shows why hex is worth the trouble: the binary is grouped into nibbles, each sitting directly under the hex digit that names it.
What 0x means. Nothing at all — it is punctuation. Since 10 could be ten in decimal or sixteen in hex, the prefix 0x announces "read what follows as hex." Binary gets 0b for the same reason. On the previous page, 0xC0000000 and 0x15faeb8 were just numbers wearing a label that said how to read them.
Bytes as text
There is nothing letter-like about the byte 01000001. It becomes the letter A because in 1963 a committee wrote down that 65 means A, and everyone agreed to use the same list.
That list is ASCII. It assigns a number to each of 128 characters — the Latin alphabet in both cases, the digits, punctuation, and control codes like newline. A few anchor points are worth knowing because they show up constantly once you start reading bytes:
| Character | Decimal | Hex | Binary | Why it matters |
|---|---|---|---|---|
| space | 32 | 0x20 | 0010 0000 | First printable character |
| 0 | 48 | 0x30 | 0011 0000 | Digits run 48–57, so the low nibble is the digit |
| A | 65 | 0x41 | 0100 0001 | Uppercase runs 65–90, alphabetically |
| a | 97 | 0x61 | 0110 0001 | Exactly 32 more than A — one single bit |
| newline | 10 | 0x0a | 0000 1010 | The \n at the end of printed output |
Look at A and a again. 0100 0001 and 0110 0001. They differ in exactly one bit — the one worth 32. That is not a coincidence; the ASCII designers arranged it deliberately so that changing case is a single bit flip rather than a table lookup. Whole categories of software still exploit this.
Type something and watch it become bytes
Beyond 127: UTF-8. ASCII only defines 128 characters, which does not get you far outside English. UTF-8 extends it by letting a character use several bytes — but it was designed so the original 128 keep their exact old values as single bytes. That is why the encoder above shows café as five bytes for four characters: the é needs two, while c, a and f are unchanged since ASCII first assigned them. (Uppercase letters were in the original 1963 standard; lowercase only arrived with the 1967 revision — so the A in the table above is four years older than the a beneath it.) Type an emoji and you will most often see four, though a few older ones — ❤ and ☺ among them — need only three.
Which brings back a line from the previous page. Python printed total = 5 and the bytes handed to the kernel were:
74 6f 74 61 6c 20 3d 20 35 0a
You can now read that directly. 74 is 116, which ASCII says is t. 6f is 111 — o. 20 is 32, a space. 35 is 53, the character 5 — not the number five, which would be 0x05. And 0a is 10, the newline. Paste total = 5 into the encoder above and the first nine bytes come back exactly; the tenth, 0a, is the newline print adds for you and cannot be typed into a single-line box.
Doing things to bits
Once numbers are rows of bits, you can operate on the bits directly rather than on the number they represent. These operations are strange the first time and obvious the second.
The key thing about AND, OR and XOR is that each column is decided entirely on its own. Bit 3 of the answer depends only on bit 3 of the two inputs. There is no carrying, no interaction between columns, nothing like ordinary arithmetic.
Click any switch in row A or B to change it.
What each one is actually for
| Operation | Rule | What it is used for |
|---|---|---|
| AND & | 1 only if both are 1 | Masking — keeping some bits and forcing the rest to zero. x & 0b00001111 keeps the low nibble and discards the high one. |
| OR | | 1 if either is 1 | Setting — turning specific bits on without disturbing the others. |
| XOR ^ | 1 if exactly one is 1 | Toggling and difference. XOR with the same value twice returns the original — the basis of a lot of cheap encryption and checksums. |
| NOT ~ | flip every bit | Inverting a mask, and building negative numbers. |
| << left | slide bits left | Multiply by 2 per position. Bits that fall off the left are gone. |
| >> right | slide bits right | Divide by 2 per position, discarding the remainder. |
Why shifting multiplies. It is the same reason adding a zero to the end of a decimal number multiplies it by ten. Each place is worth twice the one to its right, so sliding every bit one position left doubles every place value at once. 0000 0101 is 5; shift it left and you get 0000 1010, which is 10.
A mask is just a stencil. 15 in binary is 0000 1111. AND-ing anything with it gives zero everywhere the stencil is zero, and leaves the original bits everywhere the stencil is one. So x & 15 means "give me only the bottom four bits of x." Try it in the playground — set B to 15 and pick AND.
Packing several things into one number
If you have eight bits and two facts to store, you do not need two bytes. You can give each fact its own stretch of bits and keep them in one.
This is why shifts and masks exist. To pack, you shift a value left until it sits in its own territory and OR it in. To unpack, you shift right to bring your field down to the bottom, and mask to discard everything above it.
The two you have already seen
On the previous page, a CPython integer stored its digit count and its sign in one field called lv_tag. Reading the memory of the integer 2 gave 12, and the page decoded it as "one digit, positive, plus a flag bit." Now you can do that yourself: 12 is 0000 1100. Shift right by 3 and you get 1 — one digit. The three bits left behind hold the sign and a flag.
The second one is smaller and neater. The instruction LOAD_FAST_BORROW_LOAD_FAST_BORROW loads two local variables at once, so it needs to name two of them in a single argument byte. It splits the byte in half: the high nibble is the first variable's slot number, the low nibble is the second's. The mini-interpreter on that page unpacked it in one line — local[arg >> 4] and local[arg & 15] — which is exactly the operation above with the split moved to bit 4.
Why bother. Partly memory: a Python program may hold millions of integers, and a saved word each is real. But mostly it is speed — pulling two fields out of one number that is already in a CPU register costs a couple of instructions, while reading a second number from memory can cost a hundred times that. The packing exists to avoid touching RAM.
Why powers of two are everywhere
Odd-looking numbers turn up constantly in computing — 256, 1024, 65535, 4096. None of them are arbitrary. Each is the number of things a particular count of bits can address.
The KB / KiB annoyance. A kilobyte "should" be 1000 bytes, because kilo means thousand. But 1000 is not a power of two and 1024 is, so 1024 was what hardware actually gave you and the industry called it a kilobyte anyway. The units KiB, MiB, GiB were invented later to mean the powers of two unambiguously. This is why a "500 GB" drive shows up as roughly 465 GiB — nothing was stolen, the two numbers just count differently.
Reading a sentinel
One more from the previous page. Python marks immortal objects — the ones that must never be freed — by putting a specific value in their reference count field, and sys.getrefcount(2) returned 3221225472. As a decimal number that is meaningless. In hex it is 0xC0000000, and C is 1100, so the whole thing is:
1100 0000 0000 0000 0000 0000 0000 0000
Just the top two bits of a 32-bit field, and nothing else. That is the point of choosing it: a genuine reference count is a small number sitting at the bottom of the field, so a value with only the highest bits set can never be mistaken for one. The sentinel was picked to be structurally impossible, and you can see that at a glance in hex while decimal hides it completely.
Go back and read it
The other page opened a Python function and dumped its compiled bytecode as eighteen raw bytes. Here it is again.
80 00 57 01 2c 00 00 00 00 00 00 00 00 00 00 00 23 00
Each pair of hex characters is one byte, so eighteen pairs is eighteen bytes. Instructions come in twos — an opcode and its argument — so this is nine slots. And you can now convert every one of them:
| Hex | Binary | Decimal | What it is |
|---|---|---|---|
| 80 | 1000 0000 | 128 | opcode 128 — RESUME |
| 57 | 0101 0111 | 87 | opcode 87 — LOAD_FAST_BORROW_LOAD_FAST_BORROW |
| 01 | 0000 0001 | 1 | its argument — high nibble 0, low nibble 1, so locals 0 and 1 |
| 2c | 0010 1100 | 44 | opcode 44 — BINARY_OP |
| 00 | 0000 0000 | 0 | ten of these — the empty inline cache |
| 23 | 0010 0011 | 35 | opcode 35 — RETURN_VALUE |
Nothing was hidden from you. The bytes were always just numbers, written two hex digits at a time because that happens to be exactly one byte, and every one of them is a row of eight switches that are either on or off.
One habit worth forming. When you meet an unfamiliar number in a technical context, convert it to hex and look at the shape. 3221225472 tells you nothing; 0xC0000000 tells you it is a deliberately chosen sentinel. 65535 is forgettable; 0xFFFF is obviously "every bit in two bytes." Hex makes structure visible, and structure is usually the answer to the question you were asking.