Should touching slots like [9:00, 10:00) and [10:00, 11:00) merge, and how do you decide?
answer
- does the range include its end?
- half-open versus closed semantics
- sharing a point versus merely touching
- one character in the comparison decides it
- who consumes the merged output, and why
basics
~20 sSettle the convention before coding. Under half-open [start, end) semantics touching slots share no point, so collapsing them is a product decision, not a correctness one. Under closed ends they share an instant and must merge. Ask which convention the data uses.
solid answer
~50 sThere is no universal answer, and saying so out loud is the signal an interviewer is looking for. Under **half-open** `[start, end)` semantics, 10:00 belongs to the second slot only, the two ranges are disjoint, and merging them is a *presentation* choice — a free/busy strip usually wants one bar from 9 to 11, whereas a billing report may want the two blocks kept distinct. Under **closed** `[start, end]` semantics they genuinely share the instant 10:00 and must merge or the output is not disjoint. Mechanically the decision is one comparison: merge-on-touch uses `next.start <= end`, strict-overlap-only uses `next.start < end`. The real hazard is mixed conventions across producers — one synced source emitting inclusive ends against two emitting exclusive ones — which shifts every boundary by one unit and produces phantom gaps or spurious merges that no unit test on a single source will show.
go deeper
Know that [start, end) excludes its end while [start, end] includes it, and that this decides whether back-to-back slots touch. Be ready to ask which one the data uses rather than guessing.
Explain the mechanical consequence: merge-on-touch is the <= comparison, strict overlap is <. Say why half-open is the usual convention for time — adjacent ranges tile without sharing a point.
Show that you surface the ambiguity before coding and that you know collapsing adjacency is a product call under half-open semantics. Name the mixed-producer failure and normalise at ingestion.
Own it as an interface contract across teams: one convention, enforced by the type at the system boundary, documented once. Weigh the cost of normalising every producer against the recurring cost of every consumer guessing.
## Two conventions, and why the ambiguity is real An interval written `[9:00, 10:00)` is **half-open**: it contains 9:00, contains every instant up to but not including 10:00, and excludes 10:00 itself. Written `[9:00, 10:00]` it is **closed**: 10:00 is inside it. The difference looks pedantic until two ranges meet exactly at a boundary, and then it decides whether the output has one interval or two. Half-open is the dominant convention for time and for ranges over a continuum, for one strong reason: adjacent ranges tile the line without sharing any point, so a day partitions into `[9,10)`, `[10,11)`, `[11,12)` with no double counting and no gaps. Closed intervals cannot tile — either they overlap at every boundary or they leave holes. Closed is natural for inclusive discrete data instead: a row range, a page range, an inclusive date range where "through 31 March" means the whole of 31 March. ## What each convention implies for merging Under closed semantics, `[9:00, 10:00]` and `[10:00, 11:00]` intersect at 10:00. They overlap by definition, so a merge routine that is supposed to return **disjoint** intervals has no choice: it must fold them into `[9:00, 11:00]`. Failing to do so leaves an output that violates the routine's own postcondition. Under half-open semantics the same pair is disjoint. Merging them is legitimate and often desirable, but it is now a *product* decision rather than a correctness requirement: - A free/busy strip wants the merge. Two back-to-back meetings should render as one continuous busy bar; showing a hairline seam invites a scheduler to think there is a gap. - A billing or audit view usually does **not** want it. Two consecutive one-hour blocks are two chargeable events, and collapsing them destroys the count. - A capacity or utilisation report may want the raw ranges preserved and the merge computed separately, so both the union and the individual segments are available. So the honest interview answer is a question back: *are these intervals half-open or closed, and does the consumer want adjacency collapsed?* Candidates who start coding without asking are the ones whose merge routine silently disagrees with the product. ## The mechanical consequence Whichever way it lands, the decision reduces to a single comparison in the merge loop and its twin in the insert routine: | Intent | Overlap test in the merge loop | `[9,10)` and `[10,11)` | |---|---|---| | Merge on touch (required for closed intervals; a choice for half-open) | `next.start <= end` | become one interval | | Merge only on strict overlap | `next.start < end` | stay two intervals | One character. That is why this deserves to be settled and documented before the loop is written rather than discovered from a screenshot later — and why the choice must appear in a test whose name says which convention it pins, not buried as an incidental assertion. ## The failure that actually reaches production The dangerous version is not choosing wrongly; it is choosing inconsistently. A calendar service pulling busy slots from several synced sources can easily receive both conventions in one list: two sources emit exclusive ends while a third emits an inclusive end, so its 10:00-to-11:00 block arrives as an interval that includes 11:00. Every boundary from that source is then effectively one unit long, and two symptoms follow. Ranges that should be adjacent overlap by one unit and are merged, so a strip shows one long busy block where the product wanted two. Or, running strict comparisons, ranges that genuinely abut are left as two output intervals separated by a hairline gap, and a scheduler offers a one-second slot that nobody can book. Neither shows up in a unit test that feeds one source's fixtures. What catches it is normalising at the boundary: convert every incoming range to the internal convention at the point of ingestion, once, and make the internal type unable to express the other one. Validation belongs where data enters, not inside the merge loop. ## Discrete domains have a shortcut, and a trap When the coordinate is discrete — integer indices, whole days — the two conventions are inter-convertible: a closed `[a, b]` is the half-open `[a, b+1)`. Teams sometimes normalise that way to get one internal representation, which is sound. The trap is doing the conversion in more than one place, or doing it to data that is not really discrete. Timestamps at millisecond resolution look discrete until a source starts emitting microseconds, and a `+1` normalisation quietly starts adding a millisecond of phantom coverage to every range. If you normalise, do it once, name the function for what it does, and record the unit it assumes. ## What to say in the interview State both conventions, say which one you are assuming and why, name the one-character difference in the comparison, and mention that adjacency collapsing is a product decision under half-open semantics. Then note the mixed-producer hazard and where you would normalise. That sequence demonstrates the thing the question is really testing: that you surface an ambiguity in the specification before it becomes a defect.
- Which comparison in the merge loop encodes each choice?Merge-on-touch uses `next.start <= end`; strict-overlap-only uses `next.start < end`. That single character decides whether abutting ranges collapse. Under closed semantics only the first is defensible, because touching ranges genuinely share their boundary point and leaving them separate breaks the disjointness the routine promises. Under half-open semantics either is correct, so the choice should be pinned by a named test that states which convention the output follows.
- One synced source emits inclusive ends while the others emit exclusive ones. What do you do?Normalise at ingestion, not in the merge. Convert every incoming range into the single internal convention at the boundary where that source's data enters, and make the internal representation incapable of expressing the other form. Handling it inside the merge loop means every future consumer re-derives the same conversion and one of them gets it wrong. Log or reject ranges that fail validation rather than silently coercing them.
- When would you deliberately not collapse adjacent ranges?Whenever the count of ranges is itself meaningful: billing that charges per booking, audit trails where each block is an event, or utilisation reports that need per-segment attribution. Collapsing destroys information that cannot be recovered downstream. The usual resolution is to keep the raw ranges as the stored truth and compute the merged union on demand for the views that want a continuous strip.
It is the difference between a fence panel that ends at the post and one that includes the post. Two panels either meet cleanly or fight over the same post, and you cannot build the fence until someone says which.
saying these in an interview costs you the question
- Starts coding without asking which convention applies
- Says touching intervals always merge, regardless of semantics
- Treats adjacency collapsing as purely a correctness question
- Handles mixed conventions inside the merge loop
- Adds one unit to timestamps to normalise, ignoring resolution