Docs and articles
R-54
Technology
The Half-Open Interval Is a Small Choice with Big Consequences
Does a time range include its endpoint? Different databases answer differently. The wrong answer creates phantom rows or missing rows. We picked [start, end).
PAR2 Labs
August 29, 2026
1 min

We've watched a team spend an afternoon debugging why a daily summary report showed slightly wrong totals at midnight. The answer turned out to be that one of their databases used closed intervals [t1, t2] and another used half-open [t1, t2). A single transaction at exactly midnight got counted twice.
01
The choice
Every bitemporal interval in Tessera is half-open: [start, end). A record's valid_time interval includes the start instant and excludes the end instant. If a customer's address is valid from 2024-01-01T00:00:00 to 2024-12-31T23:59:59.999, the next address's valid_time starts at 2025-01-01T00:00:00 with no overlap.
02
Why half-open
No accidental double-counting. Boundaries belong to exactly one interval.
Composition is trivial. Adjacent intervals tile time without gaps or overlaps.
It matches every language's range syntax. Python's range(0, 10), Rust's 0..10, SQL's generate_series — all half-open.
The boring choices are the ones that matter most.
03
The query semantics
The convention holds everywhere history is queried. A point-in-time snapshot includes a record when its interval satisfies start <= t < end. A range query matches records whose interval overlaps the half-open window [s, e). And the same rule governs transaction-time queries as valid-time ones — one convention, no exceptions to remember.
The boring choices are the ones that matter most. Pick a convention, document it, be consistent. We picked half-open and stopped thinking about it. Recommended.
04
The one place to be careful
When ingesting from a system that uses closed intervals, the boundary point of their data is the start of the next interval in our data. Most importers handle this implicitly, but if you're hand-rolling an integration, normalise on the way in. A late-night incident report is the worst time to rediscover this.
Key Takeaways
01
Mixing closed and half-open intervals across systems double-counts anything that lands exactly on a boundary.
02
Every bitemporal interval in Tessera is half-open, [start, end): the start instant is included and the end excluded.
03
Half-open intervals tile time without gaps or overlaps and match range syntax in Python, Rust and SQL.
04
When ingesting from a closed-interval system, normalise the boundary on the way in.
PAR2 Labs · Technology
Talk to us