
You dump four bytes and see 0A 00 00 00 where you expected 00 00 00 0A. Nothing is corrupted. The transfer completed, the checksum passed, and the file is intact. The only broken thing is an assumption you didn't know you were making — that the byte at the lowest address is the one that matters most.
That assumption is called endianness, and it bites at every level of low-level work: hex dumps, network packets, file formats, struct definitions, and the one machine in the lab that isn't like the others.
Bytes have addresses; numbers have significance
A byte is simple. It has an address and a value between 0 and 255. A 32-bit integer is not simple — it needs four addresses, and something has to decide how the number's digits map onto them.
Endianness is that decision. In a little-endian machine, the least significant byte of a multi-byte value sits at the lowest address. In a big-endian machine, the most significant byte sits there instead. Both are legitimate. Both are in active use. Neither is "correct."
The same number, two memory layouts
Take the value 0x12345678, stored at address 1000:
- Little-endian: 1000 = 0x78, 1001 = 0x56, 1002 = 0x34, 1003 = 0x12
- Big-endian: 1000 = 0x12, 1001 = 0x34, 1002 = 0x56, 1003 = 0x78
Now suppose a 16-bit field in a spec is defined as 1500, which is 0x05DC. A little-endian dump shows DC 05. Read that as big-endian and you get 0xDC05 — 56325, a plausible-looking number that sends you hunting for a bug in entirely the wrong place.
The trick that saves the most time: when a field is four bytes wide and the dump looks reversed, try reversing the whole group of bytes before anything else. Whole bytes, not nibbles. Endianness never swaps the two halves of a single byte.
Who uses which, and how to tell
x86 and x86-64 are little-endian, and that's the default on most ARM and RISC-V systems you'll touch today. Plenty of architectures are bi-endian and can be configured at boot, but the operating systems running on them almost always choose little-endian.
Big-endian turns up where people don't expect it:
- TCP/IP headers, and anything that copies their fields.
- PNG and JPEG, which store their integer fields big-endian.
- MIDI and a long list of older binary formats.
- Workstation hardware from the SPARC, PowerPC and MIPS eras.
Then there are formats that refuse to choose. TIFF begins with the ASCII letters II for little-endian or MM for big-endian. ELF files record their byte order in the EI_DATA byte of the header. Mach-O encodes word size and byte order in the magic number. When a format is designed to travel between machines, someone already solved this. Read the spec's note on byte order before you read anything else in it.
Network byte order and the hton family
Network protocols standardised on big-endian, and the convention is called, plainly enough, network byte order. The sockets API gives you four conversions: htons and htonl for host-to-network, ntohs and ntohl for network-to-host. The s variants handle 16-bit values, the l variants 32-bit.
Skip them and the failure is quiet and specific. A little-endian host that writes port 80 — 0x0050 — without htons puts 50 00 on the wire. The receiver reads 0x5000, which is 20480, and rejects the connection without ever mentioning byte order.
Here's what makes it worse. On a big-endian machine, htons is effectively a no-op. Code that worked for years on one machine can break the moment it's built elsewhere, because the missing conversion was never doing anything in the first place.
Symptoms that point at byte order
Endianness bugs have a signature. Once you've seen it a few times, you recognise it in seconds.
- Values inflated by powers of 256. A length of 1 arrives as 16777216. A 16-bit count of 0x0001 arrives as 0x0100.
- Only some fields are wrong. If a single-byte field parses correctly and every wider field is garbage, look at how you're reading widths before you look at the data.
- Checksums and CRCs fail even though the payload looks right in the dump.
- Floats go strange. A float bit pattern read after a swap rarely produces NaN; it produces a tiny denormal or an enormous value. Either is a tell.
- Colors shift. Red and blue swapping in image data is often channel order rather than endianness, but a 32-bit pixel read backwards produces the same visual mess.
- It works on one machine and fails on another. Single-platform testing hides this entire class of bug.
A five-minute triage
- Hexdump the raw bytes.
- Write the bytes you expect next to them.
- Find the first field where the two disagree and reverse its bytes. If it now matches the spec, you've found it.
- Convert in exactly one place — the parse or serialise boundary — and keep everything inside the program in native order.
- Round-trip it: write a value, read it back, compare. A round-trip test catches a double swap that a one-way test never will.
Bit order is a different question
The word "endianness" gets used for things that aren't byte order. Bits inside a byte don't need an order when the byte travels as a unit — it arrives intact. Serial links do have a per-bit convention, and UART hardware sends the least significant bit of each byte first. That's worth knowing when you're decoding a logic analyser trace, but it isn't byte order.
Two more traps worth naming. C bitfields are laid out in an order the compiler chooses, and the standard doesn't pin it down, so never use them to describe a wire format. And alignment padding will cheerfully produce a struct that looks byte-swapped when it's really just padded.
Rules worth keeping
Serialise and deserialise at the edges. Inside the program, use native order and stop thinking about it. Every value that crosses a file, socket or shared-memory boundary gets an explicit pack or unpack function with the byte order named in a comment.
For the swap itself, copy bytes with memcpy into a byte buffer instead of casting pointers. That sidesteps alignment and strict-aliasing complaints at once, and most compilers will fold the shuffle into a single bswap instruction on hardware that has one. Add a compile-time guard: GCC and Clang expose __BYTE_ORDER__, so a static assertion fails the build with a clear message rather than letting you find
Photo: Brett Sayles / Pexels

