skip to content

Two consumers of one ledger feed render the booked-at timestamp in January 1970 and tens of thousands of years ahead — what single defect explains both?

level: seniorimportance: should knowfreq 52%

answer

  1. an integer that never says what it counts
  2. two symptoms, one factor of a thousand
  3. 1970 one way, year fifty thousand the other
  4. ten digits seconds, thirteen milliseconds
  5. put the unit in the field name

basics

~20 s

The contract carries a bare epoch integer without naming its unit. One consumer reads a seconds value as milliseconds and lands days after 1970; the other reads a milliseconds value as seconds and lands far in the future. The factor is one thousand.

solid answer

~40 s

A bare integer counting time since the epoch is ambiguous: seconds, milliseconds, microseconds and nanoseconds all look like the same field. The two symptoms are the two directions of the same thousand-fold error. Reading a **seconds** value as **milliseconds** divides the elapsed time by a thousand, so a present-day instant lands in mid-January 1970. Reading a **milliseconds** value as **seconds** multiplies it, so the same instant lands tens of thousands of years ahead. Both consumers are behaving correctly against a contract that never said which unit it meant. The fix is to make the unit part of the field — a name such as `bookedAtEpochMillis`, or a date-time string in a profile of ISO 8601 — and to add a plausibility guard at the boundary so an implausible instant is rejected rather than rendered.

code

json · 5 lines
json
{
  "entryId": "9007199254740993",
  "bookedAtEpochMillis": 1772323200000,
  "bookedAt": "2026-03-01T00:00:00Z"
}

go deeper

for a junior

Recall that a bare epoch integer does not say whether it counts seconds or milliseconds, and that the mismatch is a factor of a thousand in one direction or the other.

for a middle

Explain both symptoms from the arithmetic: divide by a thousand and a present-day instant falls into January 1970; multiply and it lands tens of thousands of years ahead. Know the digit counts per unit.

for a senior

Demonstrate the operational fix: the unit in the field name or a self-describing date-time string, a plausibility guard at the trust boundary, and why magnitude sniffing fails on backfill and far-dated records.

for a principal

Generalise it. Any scalar whose meaning depends on an unwritten convention — units of time, size, rate — is the same defect, and the policy is that units live in the contract and preferably in the field name.

## The field that forgot to say what it counts A timestamp on the wire is commonly an integer counting elapsed time since the Unix epoch, 1970-01-01T00:00:00Z. The integer is exact and cheap, which is why it is popular. What it does not carry is its **unit**. Seconds, milliseconds, microseconds and nanoseconds are all plausible, all whole numbers, and all indistinguishable from the field's type. The contract is the only place the unit can live, and when it does not live there, each consumer picks a default and two consumers pick different ones. ## Reading the two symptoms Both renderings in the scenario are the same defect seen from opposite ends: - **A seconds value read as milliseconds.** The elapsed count is divided by a thousand, so a present-day instant of roughly 1.77 x 10^9 seconds becomes about 1.77 x 10^6 seconds of elapsed time, which is under three weeks. Everything renders in **January 1970**, which is why a wall of suspiciously old dates is the classic tell. - **A milliseconds value read as seconds.** The count is multiplied by a thousand, so roughly 1.77 x 10^12 seconds elapse — more than fifty thousand years. Every date lands **tens of thousands of years in the future**. The digit count is the quickest field diagnosis for a present-day instant: about **10 digits** for seconds, **13** for milliseconds, **16** for microseconds, **19** for nanoseconds. ## Why the digit-count heuristic must not become the fix It is tempting to sniff the unit from the magnitude at decode time. Do not put that in the contract, for two reasons: 1. **Historical values break it.** An instant a few months after the epoch is an 8-digit number in seconds and an 11-digit one in milliseconds; neither matches the present-day pattern, so the sniffer guesses wrong precisely on the backfill data nobody tests. 2. **Far-future values break it too.** A long-dated maturity in seconds can reach into the digit range you reserved for milliseconds. A heuristic that is right for today's data and wrong for the archive is worse than an explicit unit, because it fails only after the migration. ## Precision has a second trap Even once the unit is stated, the unit interacts with the reader's numeric type. If a consumer decodes numbers into a 64-bit binary float, exact integers stop at `2^53`: | Unit | `2^53` of that unit is | Safe for present-day instants? | |---|---|---| | Seconds | about 285 million years | Yes, with enormous headroom | | Milliseconds | about 285 thousand years | Yes | | Microseconds | about 285 years | Yes, until roughly the year 2255 | | Nanoseconds | about 104 days | **No** — a present-day nanosecond instant is far past the exact range | So a nanosecond epoch field is not merely finer-grained; it is unrepresentable in such a reader and will round, losing exactly the sub-microsecond detail it was introduced to carry. ## What to put in the contract 1. **Name the unit in the field itself**, so the unit travels with the data rather than with the documentation. A suffix such as `EpochMillis` cannot be separated from the value the way a wiki page can. 2. **Or carry a date-time string** in a profile of ISO 8601 with an explicit `Z` or offset. It is longer, but it is self-describing and a wrong unit becomes a parse failure instead of a plausible date. 3. **State the epoch and the counting convention.** Unix time counts elapsed seconds ignoring leap seconds, which means the count is not a true count of SI seconds and can repeat a value across a positive leap second. Say so if the consumer is doing interval arithmetic. 4. **Guard at the boundary.** Reject any instant outside a sane window, such as more than a few years either side of now. That single check turns both symptoms in the scenario into a loud rejection at the edge instead of nonsense on a screen. 5. **State the resolution.** A field truncated to whole seconds and a field carrying milliseconds are different contracts even when both are labelled correctly, and a consumer that orders events by timestamp needs to know which. ## The wider lesson The defect class is not *time*; it is a scalar whose meaning depends on a convention that was never written down. The same failure appears with a duration field that could be seconds or milliseconds, a size field that could be bytes or kibibytes, and a rate that could be per second or per minute. Where a number's meaning depends on a unit, the unit belongs in the contract, and ideally in the field name.

  • Why not just infer the unit from the magnitude at decode time?
    Because the inference is calibrated on present-day values. An instant shortly after the epoch has 8 digits in seconds and 11 in milliseconds, and a long-dated future instant in seconds can reach the milliseconds range. The sniffer is therefore right on live traffic and wrong on the backfill and the far-dated records, which is the worst possible failure schedule.
  • A team proposes moving the field from milliseconds to nanoseconds for finer ordering. What do you check first?
    The consumers' numeric types. Where numbers decode into a 64-bit binary float, exact integers stop at 2^53, which is only about 104 days of nanoseconds, so present-day nanosecond counts round and the added precision is lost on arrival. Either carry the value as a string or as a pair of fields, or keep a coarser unit that every reader can hold exactly.
  • Does a date-time string remove every ambiguity from the field?
    It removes the unit ambiguity and makes a wrong reading a parse error rather than a plausible date. It does not by itself settle resolution — whether fractional seconds are carried and to how many places — and it does not settle what the instant means for a future local commitment. Those still have to be stated in the contract.

saying these in an interview costs you the question

  • Says the unit is obvious from the value's magnitude
  • Treats seconds as the universal default for any epoch field
  • Documents the unit only in prose outside the contract
  • Assumes a nanosecond count survives every reader exactly
  • Believes Unix epoch time counts leap seconds