API Design and Exclusive vs Inclusive Ranges

3 minute read

The documentation of an API or a function that takes a range interval should specify if the range is inclusive or not. Those could be numberic intervals, dates or other units. For instance, a range could come from a price filter in a web shop or a date picker in a reporting tool.

But couldn’t we just guess or consistently use one or the other?

The Problem

In almost all cases we would agree that the start of the range should be included, however it’s not that obvious whether the operations should include the range’s end.

If you can easily pick consecutive non-overlapping intervals, you can go either way. Why non-overlapping? Because otherwise you could be double counting the entries at the endpoints. Since there are a lot of use cases for Internet users filtering with inclusive intervals, we could decide to use inclusive intervals eveywhere, for consistency.

However, picking the next non-overlapping interval might not be that easy. If we want to split in regular inclusive intervals time between two hours, e.g. 1pm and 2pm, the end := (next start) - ε, where ε is the smallest step. Let’s assume that ε is 1 minute, then the first interval is 13:00 - 13:59, so the next one is 14:00 - 14:59. The issue becomes evident when the time resolution in the data becomes smaller and smaller, e.g. milisecond. Then the first interval becomes 13:00:00.000 - 13:59:59.999. What about nanosecond? Do you always know what time quantum the system uses? Another non-trivial example are the dates in the calendar.

Let’s look at rational numbers (ℚ), shall we. In theory you can always pick a smaller ε (e.g. divide previous ε by 10). Thus you can never pick the smallest ε. Fortunately, computers often use floating-point numbers, which are limited, however IEEE-754 floating-point numbers have their own issues:

> 0.4-0.1
0.30000000000000004
> 0.3-0.1
0.19999999999999998

Inclusive intervals are problematic, the least.

My Rule of Thumb

Use a right-open [start, end) interval that excludes the end of the range, unless there is a specific need to do otherwise.

Useful Properties of Right-Open Interval

  1. The start of the next non-overlapping interval is the end of the previous one.
  2. A right-open interval can be transformed to a closed interval. If a user wants an inclusive range, the adjustment of the range’s end can be done in the frontend application.
  3. The length of an integer interval is end - start. For instance, the count of the numbers between 3 and 10 (excluded) is 10 - 3 = 7.
  4. You can have an empty interval, when start = end. In contrast, assuming that start <= end, inclusive ranges are always non-empty and contain at least one element.

Other Examples

In Python, there is a function range(start, end) that generates numbers between start and end, numbers that are < end. This way range(3, 7) would yield the numbers in the list [3, 4, 5, 6].

In Go, Python and JavaScript, slicing an array uses a right-open interval. That’s array.slice(start, end) in JS and array[start:end] in Go and Python.

In C, there are a lot of loops that look like for (i=0; i<end; i++) {. It’s easy to reason about them. The last loop body will execute when i = end - 1 and after the loop is finished i would be equal to end.