How to Read Hex, Binary, and Octal When They Show Up in Real Code
A practical guide to hex, binary, and octal as they appear in debuggers, network packets, file permissions, and source code — with real examples you can decode right now.
How to Read Hex, Binary, and Octal When They Show Up in Real Code
Most developers learn number bases in a CS class, forget them, and then panic the first time a hex dump appears in a production incident. I've been there: staring at 0x0000_7FFF in a core dump at 11 PM, mentally trying to run a base-16 conversion while also reading three stack frames simultaneously.
The bases themselves aren't hard. What's hard is pattern-matching them instantly when the context is already noisy. This guide focuses on exactly those moments — the places hex, binary, and octal actually appear in code you'll write or read, and the mental moves that make them readable without reaching for a calculator.
The Three Bases, and Where Each One Lives
Before diving into examples, a quick anchor:
- Binary (base 2,
0bprefix) — the native language of hardware. Every byte in RAM, every packet on the wire, every CPU flag is ultimately binary. You'll rarely write raw binary literals outside of embedded C or low-level Rust, but you'll read it whenever you interpret bit fields. - Hexadecimal (base 16,
0xprefix) — the human-readable shorthand for binary. Four bits map to one hex digit with no rounding, so an 8-bit byte is always two hex characters. This is why memory addresses, color codes, and checksums are in hex: they're dense and predictable. - Octal (base 8,
0prefix in C/JS,0oin Python 3) — you'll use it almost exclusively for Unix file permissions. Its 3-bit grouping matches the owner/group/world triad exactly, makingchmod 0755readable once you know the pattern.
Hex in Practice: Colors, Checksums, and Addresses
CSS Color Codes
The most common hex values any frontend or fullstack developer sees are color codes. #FF5733 breaks down as:
FF= red = 255 decimal =1111 1111binary57= green = 87 decimal =0101 0111binary33= blue = 51 decimal =0011 0011binary
The shorthand #F53 expands to #FF5533: each digit doubles. Knowing this, when a designer hands you #1a1a2e, you can immediately read it as dark blue (low red 26, low green 26, medium-low blue 46) without converting — the first two digits being small tells you "dark", the last two being larger tells you "more blue than red or green."
Checksums and Hashes
Git commit hashes, SHA-256 digests, and API ETag values are all hex. A SHA-256 output is 256 bits = 32 bytes = 64 hex characters — each 4-bit nibble maps to one hex digit, so no information is lost in translation.
If you see a truncated hash like a3f8c2 in a log, it's the first 24 bits of the full hash. Git's minimum abbreviated hash length is 4 characters (16 bits), giving a collision probability under 1 in 65,000 for small repositories.
Memory Addresses
Debuggers and stack traces print addresses in hex:
#0 0x00007ffee4b2c3a0 in main ()
#1 0x00007ffee4b2c3b8 in __libc_start_main ()
0x00007ffee4b2c3a0 is a 64-bit address. The leading 0x0000 tells you it's in user space — kernel addresses on x86-64 Linux have the high bits set (0xffff...). The two addresses differ by 0x18 = 24 bytes, which is the stack frame size for that call. This kind of quick subtraction in hex becomes natural once you practice it. You use the base converter to verify until the patterns stick.
Binary for Bit Flags
The scenario I hit most often in embedded code and kernel programming is reading and setting individual bits in a register or status byte.
Consider a hypothetical USB device status register documented as:
| Bit | Meaning | |-----|---------------| | 7 | Device ready | | 4 | Error flag | | 1 | Data pending | | 0 | Busy |
If the register reads 0x93, what's set?
0x93 = 1001 0011 in binary.
Reading right to left:
- Bit 0 = 1 → Busy
- Bit 1 = 1 → Data pending
- Bit 4 = 0 → No error
- Bit 7 = 1 → Device ready
I tested exactly this pattern while debugging an I2C sensor driver that was intermittently returning stale values. The status byte was 0x83 = 1000 0011, meaning "ready and busy but no data pending" — device ready but mid-transaction. The fix was to wait for bit 1 before reading. Without reading the binary representation, the hex value 0x83 would have meant nothing to me.
The pattern: convert hex → binary when you need to inspect individual bits. Use the bitwise calculator when you need to mask, shift, or combine multiple values — it shows the binary representation inline so you can verify the operation.
Octal for File Permissions
Octal's entire modern use case is chmod. The permission string -rwxr-xr-- maps to octal 0754:
- Owner:
rwx= 4+2+1 = 7 - Group:
r-x= 4+0+1 = 5 - World:
r--= 4+0+0 = 4
In binary: each digit is three bits, and each bit is read/write/execute respectively. 7 = 111 (all three), 5 = 101 (read and execute), 4 = 100 (read only).
The mistake I see most often is confusing chmod 777 (all permissions for everyone) with chmod 755 (all for owner, read/execute for everyone else). The difference is bit 1 in the last two digits — the write bit for group and world. Setting 0777 on a production web root has caused more than a few incidents.
One gotcha: in JavaScript and C, a numeric literal starting with 0 is octal. 0755 in a C chmod call is correct. But parseInt("0755") in older JavaScript returns 493 (decimal 755), not octal, because parseInt ignores the leading zero without a radix. Use parseInt("755", 8) to be explicit, or 0o755 in modern JS/Python 3.
Combining Bases: A Real Debugging Session
Here's a scenario that pulls all three bases together.
You're reading a packet capture. The first byte of a TLS record is 0x16. What is it?
0x16hex =22decimal =0001 0110binary- TLS record type 22 = Handshake (per RFC 8446 §5.1)
The next two bytes are 0x03 0x03. That's 3 and 3 in decimal — TLS version 1.2 (despite TLS 1.3 using the same field for compatibility).
The next two bytes are the record length: 0x00 0xF4 = 0 × 256 + 244 = 244 bytes.
So the five-byte TLS record header 16 03 03 00 F4 tells you: "This is a 244-byte handshake message." You decoded it entirely using hex-to-decimal conversion — no binary needed here because the fields are byte-aligned, not bit-packed.
When fields become bit-packed (as in IPv4 headers, where a 4-bit version field and 4-bit IHL share one byte), you need binary. The first byte of an IPv4 packet is almost always 0x45 = 0100 0101 — version 4, IHL 5 (meaning 20-byte header, no options).
Quick Mental Shortcuts
These have saved me more time than any cheat sheet:
- Hex
8= binary1000— the high bit of a nibble.0x80always sets bit 7. - Hex
F= binary1111— all four bits set.0xFFis always 255, always a full byte. - Odd hex = odd binary — the last hex digit's last bit is always the byte's LSB.
0x7ends in111, so it's odd. - Shift-by-4 = multiply/divide hex —
0x10is0x01shifted left 4 bits. One hex character is four bits. - Octal per group of 3 bits — split binary right-to-left in groups of three to read octal.
0b001111101→001 111 101→1 7 5octal.
For anything beyond single-nibble mental arithmetic, I open the base converter — it handles arbitrary bit widths and shows binary, hex, octal, and decimal simultaneously, which is exactly what you need when reading a register dump.
When to Use Which Base
- Reading addresses, hashes, and color codes: hex, always.
- Inspecting individual bits in a status register or flag byte: convert to binary first.
- File permissions: octal.
- Arithmetic and overflow checks: decimal or hex, depending on whether you care about bit boundaries.
- Packed bit fields in network protocols: hex to label the byte, binary to read the field.
The bases aren't competing notations — they're lenses for different parts of the same value. The more you switch between them deliberately, the faster the pattern matching becomes automatic.
Made by Toolora · Updated 2026-06-29