Interval Notation: Half-Open vs Closed

What are Half-Open Intervals?

Notation: [start, end) where:

Visual Examples

Example 1: Simple Range

GridRange: { startRowIndex: 0, endRowIndex: 5 }

Means: [0, 5) = rows 0, 1, 2, 3, 4

Visual:
Row  Included?
 0   โœ… YES (>= start)
 1   โœ… YES
 2   โœ… YES
 3   โœ… YES
 4   โœ… YES
 5   โŒ NO  (>= end, excluded!)
 6   โŒ NO

Common mistake: Thinking row 5 is included!

Example 2: Single Row

GridRange: { startRowIndex: 3, endRowIndex: 4 }

Means: [3, 4) = row 3 ONLY

Visual:
Row  Included?
 2   โŒ NO
 3   โœ… YES (only this one!)
 4   โŒ NO  (excluded)
 5   โŒ NO

Example 3: Empty Range

GridRange: { startRowIndex: 5, endRowIndex: 5 }

Means: [5, 5) = EMPTY (zero rows!)

This is VALID! Empty ranges are allowed.

Visual:
Row  Included?
 4   โŒ NO
 5   โŒ NO  (start == end โ†’ empty!)
 6   โŒ NO

Why Half-Open Intervals?

1. Adjacent Ranges Don't Overlap

โœ… Half-open:
Range A: [0, 5) = rows 0-4
Range B: [5, 10) = rows 5-9

        NO GAP, NO OVERLAP!

โŒ Closed intervals:
Range A: [0, 4] = rows 0-4
Range B: [5, 9] = rows 5-9

        GAP at row 4.5? NO!

OR:

Range A: [0, 5] = rows 0-5
Range B: [5, 10] = rows 5-10

        OVERLAP at row 5! Ambiguous!

2. Range Length is Simple

โœ… Half-open: length = end - start
   [0, 5) has length 5 - 0 = 5 โœ“

โŒ Closed: length = end - start + 1
   [0, 4] has length 4 - 0 + 1 = 5 (extra +1 is error-prone!)

3. Matches Programming Conventions

Array slicing:  arr[0:5] means indices 0-4
Python range:   range(0, 5) yields 0,1,2,3,4
Google Sheets:  GridRange uses half-open
Our library:    Matches the API!

Common Mistakes & How to Avoid

Mistake 1: Including the End

โŒ WRONG:
"I want rows 0 through 5"
โ†’ endRowIndex: 5

Result: Gets rows 0-4 (missing row 5!)

โœ… CORRECT:
"I want rows 0 through 5"
โ†’ endRowIndex: 6 (one more than you want!)

Result: [0, 6) = rows 0-5 โœ“

Mistake 2: Off-by-One on Single Cell

โŒ WRONG:
"I want cell A1 (row 0, col 0)"
โ†’ { startRowIndex: 0, endRowIndex: 0 }

Result: [0, 0) = EMPTY!

โœ… CORRECT:
"I want cell A1"
โ†’ { startRowIndex: 0, endRowIndex: 1 }

Result: [0, 1) = row 0 only โœ“

Mistake 3: Assuming Inclusive

โŒ WRONG thinking:
endRowIndex: 5 means "up to and including row 5"

Result: Confusion when row 5 isn't included!

โœ… CORRECT thinking:
endRowIndex: 5 means "up to but NOT including row 5"
          = "stop BEFORE row 5"
          = "last row is 4"

Mnemonic: "end is where you STOP, not where you INCLUDE"

2D Example: Full Grid Range

GridRange:
{
    startRowIndex: 2,
    endRowIndex: 5,
    startColumnIndex: 1,
    endColumnIndex: 4
}

Means: [2, 5) ร— [1, 4)
     = rows 2,3,4 ร— columns 1,2,3

Visual Grid:
       col 0   col 1   col 2   col 3   col 4
row 0    ยท       ยท       ยท       ยท       ยท
row 1    ยท       ยท       ยท       ยท       ยท
row 2    ยท       โœ…      โœ…      โœ…      ยท
row 3    ยท       โœ…      โœ…      โœ…      ยท
row 4    ยท       โœ…      โœ…      โœ…      ยท
row 5    ยท       ยท       ยท       ยท       ยท

Covers 3 rows ร— 3 columns = 9 cells

Converting Between Notations

Half-Open โ†’ Inclusive (Internal Storage)

// GridRange uses [start, end)
const gridRange = { startRowIndex: 0, endRowIndex: 5 };

// Convert to inclusive [min, max] for internal math
const inclusive = [
    gridRange.startRowIndex,      // 0 (same)
    gridRange.endRowIndex - 1     // 5-1 = 4 (subtract 1!)
];

Result: [0, 4] in inclusive notation

Inclusive โ†’ Half-Open (API Return)

// Internal: [0, 4] inclusive
const inclusive = [0, 4];

// Convert to GridRange [start, end)
const gridRange = {
    startRowIndex: inclusive[0],      // 0 (same)
    endRowIndex: inclusive[1] + 1     // 4+1 = 5 (add 1!)
};

Result: [0, 5) in half-open notation

Quick Reference

Want GridRange Setting Result
Single cell (0,0) start: 0, end: 1 [0, 1)
Row 5 only startRow: 5, endRow: 6 [5, 6)
Rows 0-9 (10 rows) startRow: 0, endRow: 10 [0, 10)
Entire column A (0) startCol: 0, endCol: 1 [0, 1)
Empty range startRow: 5, endRow: 5 [5, 5)
Adjacent ranges [0,5) then [5,10) No gap!

Remember: end is always "one past" what you want!


See Also: