The Problem

The elves' new inventory system has a database of fresh ingredient ID ranges. You need to figure out which ingredients are fresh and which are spoiled.

Summary

Part

Question

Strategy

1

How many available ingredients are fresh?

Check each ID against all ranges

2

How many IDs total are considered fresh?

Merge overlapping ranges, sum their sizes

Part 1: Check Against Ranges

Goal: For each available ingredient ID, check if it falls within any fresh range.

Ranges
Parsed0
Fresh0
3-5
10-14
16-20
12-18
Original Ranges
Test IDs
1
5
8
11
17
32
Event Log
Waiting for events...

Simple linear scan - for each ingredient, check all ranges:

function isInAnyRange(id: number, ranges: Range[]): boolean {
    for (const range of ranges) {
        if (id >= range.start && id <= range.end) return true;
    }
    return false;
}

Part 2: Count All Fresh IDs

Goal: Ignore the ingredients list. Count all IDs covered by the ranges.

The catch? Ranges can overlap! We can't just sum up (end - start + 1) for each range - we'd count overlapping IDs multiple times.

Ranges
Parsed0
Merged0
Total IDs0
3-5
10-14
16-20
12-18
Original Ranges
Merged Ranges
Event Log
Waiting for events...

The Solution: Merge Overlapping Ranges

  1. Sort ranges by start position

  2. Merge overlapping/adjacent ranges

  3. Sum the sizes of merged ranges

function mergeRanges(ranges: Range[]): Range[] {
    if (ranges.length === 0) return [];

    const sorted = [...ranges].sort((a, b) => a.start - b.start);
    const merged: Range[] = [sorted[0]];

    for (let i = 1; i < sorted.length; i++) {
        const current = sorted[i];
        const last = merged[merged.length - 1];

        // Overlapping or adjacent? Extend the current merged range
        if (current.start <= last.end + 1) {
            last.end = Math.max(last.end, current.end);
        } else {
            // Gap between ranges - start a new one
            merged.push(current);
        }
    }

    return merged;
}

Visual Example

After sorting: [3-5], [10-14], [12-18], [16-20]

Step 1: Start with [3-5]
Step 2: [10-14] doesn't overlap with [3-5] → Add new range
Step 3: [12-18] overlaps with [10-14] → Extend to [10-18]
Step 4: [16-20] overlaps with [10-18] → Extend to [10-20]

Merged: [3-5], [10-20]

Why last.end + 1?

Adjacent ranges should also merge! Range 3-5 and 6-8 together cover 3, 4, 5, 6, 7, 8 - no gaps.

Performance

Part

Time

Part 1

1.31ms

Part 2

295µs

Part 2 is faster because we just process the ranges once (O(n log n) for sorting), while Part 1 checks each ingredient against all ranges.