Module 01 · Machine basics · Lesson 3 of 4
How data is represented
Bits, bytes and the encodings behind every value: binary and hex, integer sizes and overflow, two's complement, floating point and why 0.1 + 0.2 is not 0.3, text as UTF-8, and byte order. The representation bugs that reach production.
Why it matters
Some of the most expensive production bugs are representation bugs: an order ID that overflowed a 32-bit integer, money that drifted by a cent because it was a float, a username limit that cut an emoji in half, a timestamp that will wrap in 2038. None needs deep theory to avoid. They need you to know how a value is stored and what its limits are.
The idea
Everything in memory is bits, grouped into 8-bit bytes. A type is an agreement about how to interpret a run of bytes: as an unsigned integer, a signed integer, a floating-point number, or a sequence of characters. The same four bytes mean different things under different agreements, and each agreement has edges.
Binary and hexadecimal
Binary is base 2: 00101010 is 32 + 8 + 2 = 42. Hexadecimal is base 16 and maps exactly four bits to one digit, so a byte is always two hex digits: 42 is 0x2A. Hex is how memory addresses, colours, hashes and byte dumps are written because it is compact and lines up with bytes.
Integers and overflow
| Type | Bytes | Range | Runs out when… |
|---|---|---|---|
| Signed 32-bit int | 4 | about −2.1 billion to +2.1 billion | A table's auto-increment ID passes 2,147,483,647 |
| Unsigned 32-bit | 4 | 0 to about 4.3 billion | IPv4 addresses ran out at this size |
| Signed 64-bit | 8 | about ±9.2 × 10^18 | Practically never for counters and IDs |
| 32-bit Unix time (seconds) | 4 | up to 19 January 2038 | Systems still storing time in signed 32 bits |
Signed integers use two's complement: the top bit carries a negative weight, so adding 1 to the largest positive value wraps to the most negative one rather than failing. Some languages wrap silently, some throw, some treat it as undefined behaviour. The defence is choosing 64-bit types for IDs, counters and timestamps by default, and checking arithmetic on untrusted inputs.
Floating point
Floats (IEEE 754) store a sign, an exponent and a fraction in binary, like scientific notation in base 2. They cover huge ranges with about 7 (32-bit) or 15 to 16 (64-bit) significant decimal digits. The catch: most decimal fractions, including 0.1, have no exact binary representation, so 0.1 + 0.2 yields 0.30000000000000004. Errors accumulate across many operations, and comparing floats with == is unreliable.
| Value type | Use | Avoid |
|---|---|---|
| Money | Integer minor units (cents) or a decimal type, plus a currency code | float and double |
| Measurements, ML, graphics | float or double | Exact equality comparisons |
| IDs and counters | 64-bit integers, or opaque strings | 32-bit integers; floats (JavaScript numbers lose precision above 2^53) |
| Timestamps | 64-bit epoch milliseconds or an ISO 8601 string in UTC | 32-bit seconds; local time without a zone |
Text
A character set assigns numbers to characters (Unicode has over a million code points); an encoding turns those numbers into bytes. UTF-8, the near-universal default, uses one byte for ASCII and two to four bytes for everything else. So string length in bytes, in code points and in visible characters can all differ: an emoji family can be one visible character, several code points and over twenty bytes. Database column limits, API size limits and truncation must say which they mean, and truncation must never cut in the middle of a character.
Byte order and alignment
Multi-byte numbers can be stored with the most significant byte first (big-endian, also called network byte order, used in network protocols) or least significant first (little-endian, used by x86 and most ARM chips). It matters whenever bytes cross between machines or files are parsed by hand; serialisation formats specify it so you do not have to. Compilers also align fields to their natural boundaries and insert padding, which is why a struct can be larger than the sum of its fields.
Trade-offs
Wider types cost more memory and bandwidth but remove overflow risk; at today's sizes the safety is almost always worth it for IDs and timestamps. Floats trade exactness for range and speed; decimals trade speed for exactness. UTF-8 trades fixed width for compactness and ASCII compatibility, which is why every length limit must name its unit.
In an interview
Volunteer representations when designing schemas and APIs: "IDs are 64-bit, generated time-sortable; money is integer cents with a currency code; timestamps are UTC epoch milliseconds; text is UTF-8 with limits in characters, enforced on code-point boundaries." Mentioning that JavaScript clients lose precision on 64-bit integers, so IDs go over the wire as strings, is a detail that lands well.
Check yourself
An orders table uses a signed 32-bit auto-increment primary key and inserts about 5 million orders a day across all tenants. What happens, and when?