cswork / libraryOpen practice workspace

Algorithm tutorials

Endianness: Byte Order in Memory, Files and Networks

What little-endian and big-endian mean, when byte order matters (raw bytes, files, network protocols) and when it does not, how to detect it, and how to swap or decode bytes portably in Python and C++.

Endianness is an extension topic beyond most algorithm interviews; it pays off in systems, embedded, networking and storage interviews, where "how would you detect the byte order of this machine?" and "swap the bytes of a 32-bit integer" are classic warm-ups.

Intuition

A multi-byte value such as the 32-bit integer 0x01234567 occupies four consecutive bytes in memory. The most significant byte is 0x01, the least significant is 0x67. Endianness is the order in which those bytes are laid out at increasing addresses:

Address+0+1+2+3
Little-endian67452301
Big-endian01234567
  • Little-endian stores the least significant byte first. x86 and x86-64 processors, and ARM processors as normally configured (phones, Apple silicon), are little-endian.
  • Big-endian stores the most significant byte first. It is the network byte order used by IP, TCP and UDP headers, and many file formats and older architectures use it.
  • Endianness concerns the order of bytes, not of bits inside a byte, and neither order is "better"; the names come from a satirical argument about which end of an egg to crack.

When it matters and when it does not:

  • It does not matter for arithmetic, comparisons or shifts on values held in variables. x >> 8 means "divide by 256" on every machine.
  • It matters whenever a value crosses the boundary between "number" and "raw bytes": writing integers to a binary file, sending them over a network, reinterpreting a buffer as an integer, hashing the memory of a struct, or reading a binary format written by another machine.

The portable rule: decode and encode multi-byte fields with shifts and masks in an explicitly chosen order (or a library call that names the order), never by copying raw memory into an integer and hoping the host agrees.

When to use it in interviews / OAs

  • "Write a function that detects whether the machine is little- or big-endian."
  • "Reverse the bytes of a 32-bit integer" (a cousin of "reverse the bits").
  • Designing a binary protocol or file format: specify the byte order of every field, usually network order.
  • Debugging: a value read from a file or socket looks scrambled (0x01000000 instead of 1), which is the signature of a byte-order mismatch.

Template

Python

int.to_bytes, int.from_bytes and the struct module name the byte order explicitly: "<" little-endian, ">" or "!" big-endian (network), "=" the host's native order.

import struct
import sys

value = 0x01234567
little = value.to_bytes(4, "little")  # bytes 67 45 23 01: least significant first
big = value.to_bytes(4, "big")        # bytes 01 23 45 67: network byte order
assert struct.pack("<I", value) == little
assert struct.pack(">I", value) == big == struct.pack("!I", value)
assert int.from_bytes(little, "little") == value == int.from_bytes(big, "big")
assert struct.pack("=I", value) == (little if sys.byteorder == "little" else big)  # host order

Swapping byte order with shifts and masks, plus the one-line library version for any width:

def swap32(x: int) -> int:
    """Reverse the byte order of a 32-bit unsigned integer."""
    return (((x & 0x000000FF) << 24)
            | ((x & 0x0000FF00) << 8)
            | ((x & 0x00FF0000) >> 8)
            | ((x & 0xFF000000) >> 24))


def swap_bytes(x: int, width: int) -> int:
    return int.from_bytes(x.to_bytes(width, "little"), "big")

Reading a binary header whose fields are in network byte order (version: 1 byte, flags: 1 byte, length: 2 bytes, id: 4 bytes):

def parse_header(packet: bytes) -> tuple[int, int, int]:
    version, _flags, length, ident = struct.unpack("!BBHI", packet[:8])
    return version, length, ident

C++

#include <cstdint>
#include <cstring>

bool is_little_endian() {
  std::uint32_t one = 1;
  unsigned char first;
  std::memcpy(&first, &one, 1);  // the byte stored at the lowest address
  return first == 1;
}

std::uint32_t swap32(std::uint32_t x) {
  return (x >> 24) | ((x >> 8) & 0x0000FF00u) | ((x << 8) & 0x00FF0000u) | (x << 24);
}

// Decoding with shifts works on any host, whatever its own byte order.
std::uint32_t read_be32(const unsigned char* p) {
  return (std::uint32_t(p[0]) << 24) | (std::uint32_t(p[1]) << 16) |
         (std::uint32_t(p[2]) << 8) | std::uint32_t(p[3]);
}

C++20 also offers std::endian::native in <bit> and C++23 std::byteswap; POSIX code uses htonl, ntohl, htons and ntohs to convert between host and network order.

Complexity

Every operation here is O(number of bytes), effectively O(1) for fixed-width integers. Swapping a buffer of n values is O(n).

Common pitfalls

  • Casting pointers to read integers (*(uint32_t*)buf) depends on the host's byte order, may violate alignment, and breaks strict-aliasing rules in C and C++. Use memcpy plus an explicit conversion, or decode with shifts.
  • Forgetting to convert in both directions: htonl when sending, ntohl when receiving. On a big-endian host both are no-ops, which hides the bug until the code runs on a little-endian one.
  • Swapping fields that are single bytes or strings. Only multi-byte numeric fields have a byte order; text in UTF-8 is a sequence of bytes and is not swapped.
  • Confusing byte order with bit order. Reversing the bits of an integer is a different operation from reversing its bytes.
  • Sign extension when assembling bytes in C or Java: promote each byte to an unsigned type before shifting, or mask it with 0xFF.
  • Assuming one order for floating-point values. Floats follow the platform's byte order too; serialize them with an explicit format (struct.pack("<d", x)) or as text.

Interview variations

  • Detect the host byte order: the memcpy check above, or sys.byteorder in Python.
  • Reverse the bytes of an integer and reverse the bits of an integer: shifts and masks (bits can also be reversed with a divide-and-conquer swap of halves, quarters, and so on).
  • Convert between host and network order, and explain why protocols fix one order.
  • Design a binary serialization format: fixed-width big-endian integers, length-prefixed strings, a version field first.
  • Debug a corrupted value read from a file written on a different machine: compare against the byte-swapped value.