What do int.to_bytes and int.from_bytes do, and why does the byteorder argument matter?
answer
- Which end carries the big digits
- Wrong choice gives no exception
- Width is exact, not best effort
- Two's complement needs an explicit flag
basics
~20 sint.to_bytes renders an integer as a fixed-width bytes value and int.from_bytes reads one back. The byteorder argument, "big" or "little", picks which end holds the most significant byte; getting it wrong silently yields a different number rather than an error.
solid answer
~40 s`int.to_bytes(length, byteorder, signed=False)` produces exactly `length` octets, raising `OverflowError` rather than truncating if the value does not fit, and `int.from_bytes` is the inverse. `byteorder` must be `"big"` or `"little"`: 4096 is `00 00 10 00` big-endian and `00 10 00 00` little-endian, and mixing the two corrupts data silently instead of failing. `sys.byteorder` gives the native order, which is right for native memory and wrong to bake into a wire format — state the order explicitly there. `signed=True` switches to two's complement, so `b"\xff"` reads as `-1` rather than `255`. Since 3.11 both methods default to `length=1` and `byteorder="big"`. For readable dumps and fixtures, `bytes.hex()` and `bytes.fromhex()` round-trip the same octets as text.
code
python · 6 linesn = 4096
print(n.to_bytes(4, "big").hex(" "), n.to_bytes(4, "little").hex(" "))
print(int.from_bytes(n.to_bytes(4, "big"), "big"),
int.from_bytes(n.to_bytes(4, "big"), "little"))
print(int.from_bytes(bytes.fromhex("ff"), "big", signed=True))
print((-2).to_bytes(2, "big", signed=True).hex())go deeper
Recall that these two methods convert between an integer and a fixed-width bytes value, and that you must say how many bytes and which end holds the most significant one. Do not guess the byte order — read it from the format you are implementing.
Explain the arguments and their failure modes: exact length with OverflowError instead of truncation, signed switching to two's complement, and the fact that a wrong byteorder produces a wrong number rather than an exception.
Show the habits that keep binary code debuggable: byteorder and signedness stated explicitly at every call site, widths documented alongside the format, hex fixtures in tests, and decoding straight out of a memoryview slice rather than copying each field.
Own the format decision itself — where byte order and signedness are specified, whether hand-rolled integer packing is appropriate at all versus a declarative layer, and how the choice is versioned once other systems parse the bytes you emit.
### The two calls `int.to_bytes(length, byteorder, *, signed=False)` renders an integer as a fixed-width `bytes` object; `int.from_bytes(data, byteorder, *, signed=False)` reads one back. They are the built-in way to move integers across a boundary that speaks octets — a binary record header, a length prefix, a checksum field, a fixed-width key — without pulling in a formatting layer. `byteorder` is `"big"` or `"little"` and decides which end of the byte string carries the most significant byte. The value 4096 is `0x1000`; in four bytes big-endian that is `00 00 10 00`, and little-endian it is `00 10 00 00`. Get it backwards and you do not get an error, you get a plausible wrong number: reading those big-endian bytes as little-endian yields 1048576. Silent corruption is exactly why interviewers ask. `sys.byteorder` reports the machine's native order (`"little"` on essentially every mainstream CPU today). It is the right thing to pass when you are talking to native memory on the same machine, and the wrong thing to hardcode into a file format or a network message, where the order must be part of the specification and stated explicitly at every call site. ### Width, signedness and the errors `length` is exact — you get precisely that many bytes, zero-padded on the correct end. If the value does not fit, `to_bytes` raises `OverflowError` rather than truncating. That is a feature: a serializer that silently drops the high bytes produces data nobody can debug later. If you want truncation you must mask deliberately. `signed=False` is the default, so negative values raise `OverflowError` on the way out, and on the way back a leading `0xFF` decodes as a large positive number rather than a negative one. With `signed=True` the encoding is two's complement: `(-2).to_bytes(2, "big", signed=True)` is `fffe`, and `int.from_bytes(b"\xff", "big", signed=True)` is `-1` where the unsigned read gives `255`. The signedness must match on both sides of the wire, and it is not discoverable from the bytes. Since Python 3.11 both methods have defaults — `length=1` and `byteorder="big"` — so `(5).to_bytes()` is `b'\x05'`. The defaults are a convenience for one-byte work; in protocol code, pass both arguments explicitly so the reader can see the framing without checking the version. `from_bytes` accepts any bytes-like object, so a `bytes`, a `bytearray`, a `memoryview` slice of a larger frame, or an iterable of integers all work — which means you can decode a field straight out of a zero-copy window without materializing it first. It also has no width limit: Python integers are arbitrary precision, so a 64-byte value decodes into a very large `int` without overflow. ### hex and fromhex, the human-readable pair `bytes.hex()` renders octets as a lowercase hex `str`, and `bytes.fromhex()` parses one back; `bytearray` has the same pair. `hex()` takes an optional separator argument — `data.hex(" ")` gives `00 00 10 00` — which is the fastest way to make a binary dump readable in a log or a test failure. `fromhex` tolerates whitespace between byte pairs, so it round-trips a spaced dump, and raises `ValueError` on an odd number of digits or a non-hex character. The important framing is that hex is a **representation of bytes**, not an encoding of text and not a checksum. `b"\xff".hex()` is the two-character string `"ff"`; it has doubled in size and carries no more information. Use it for logs, fixtures, config values and test assertions, never as a transport format where the doubling matters. A candidate who describes `hex()` as "encoding the bytes" has blurred a distinction that matters everywhere else in this area. ### Putting it together A typical binary header is written as a sequence of explicit `to_bytes` calls into a `bytearray` and read back with `from_bytes` over `memoryview` slices of the received frame. Both sides state `byteorder` and `signed` at every call, both sides state widths, and the test fixtures are written as `bytes.fromhex("...")` so the expected wire bytes are readable in the source. For anything more than a handful of fields, a declarative packing layer in the standard library is the better tool, but `to_bytes` and `from_bytes` remain the primitives underneath, and they are what an interviewer expects you to reach for when the field count is small.
- What happens if the integer does not fit in the requested length?`to_bytes` raises `OverflowError: int too big to convert` — it never truncates. That is deliberate: a serializer that quietly dropped high-order bytes would produce corrupt records that only surface much later. If truncation is genuinely wanted, mask the value first, so the intent is visible in the code.
- When is sys.byteorder the right thing to pass?When you are talking to memory on the same machine — reinterpreting a native buffer, or matching a C struct laid out by the local process. It is the wrong thing for a file format or a network message, where the byte order belongs to the specification. Hardcoding `"big"` or `"little"` at those call sites makes the framing readable and portable.
- Is bytes.hex() an encoding of the data?No. It is a textual representation of octets: `b"\xff".hex()` is the two-character `str` `"ff"`, twice the size and no more information. Use it for logs, fixtures and config, not as a transport format. Its inverse, `bytes.fromhex()`, tolerates whitespace between pairs and raises `ValueError` on odd digit counts or non-hex characters.
saying these in an interview costs you the question
- Assumes the platform byte order applies on the wire
- Forgets signed=True and reads 0xFF as 255
- Expects to_bytes to truncate rather than raise OverflowError
- Calls bytes.hex() an encoding of the data
- Believes int.from_bytes only accepts a bytes object
- Thinks byte order is discoverable from the bytes themselves