← run

js-02-merge-intervals

1.000
10/10 tests· algorithms
Challenge · difficulty 2/5
# Merge overlapping intervals

Implement an ES module **`solution.js`** exporting a single function:

```js
export function mergeIntervals(intervals) { /* ... */ }
```

`intervals` is an array of `[start, end]` pairs of numbers, each a **closed** interval with
`start <= end`. Merge all overlapping intervals and return a new array of merged intervals
**sorted ascending by start**.

Rules:
- Intervals that **touch** are merged: `[1, 3]` and `[3, 5]` become `[1, 5]`.
- A merged interval's end is the **maximum** end of the intervals that went into it (handle full
  containment, e.g. `[1, 10]` swallows `[2, 3]`).
- The input may be in any order and may contain duplicates. Do **not** mutate the input array.
- An empty input returns `[]`.

Examples:
```js
mergeIntervals([[1, 4], [2, 5]])            // => [[1, 5]]
mergeIntervals([[3, 5], [1, 2]])            // => [[1, 2], [3, 5]]
mergeIntervals([[1, 3], [3, 5]])            // => [[1, 5]]
mergeIntervals([[1, 10], [2, 3], [4, 8]])   // => [[1, 10]]
mergeIntervals([])                          // => []
```
tests/solution.test.js
import { test } from "node:test";
import { strict as assert } from "node:assert";
import { mergeIntervals } from "./solution.js";

test("empty input returns empty array", () => {
  assert.deepEqual(mergeIntervals([]), []);
});

test("single interval is returned unchanged", () => {
  assert.deepEqual(mergeIntervals([[1, 3]]), [[1, 3]]);
});

test("unsorted disjoint input is sorted", () => {
  assert.deepEqual(mergeIntervals([[3, 5], [1, 2]]), [[1, 2], [3, 5]]);
});

test("overlapping intervals merge", () => {
  assert.deepEqual(mergeIntervals([[1, 4], [2, 5]]), [[1, 5]]);
});

test("touching endpoints merge", () => {
  assert.deepEqual(mergeIntervals([[1, 3], [3, 5]]), [[1, 5]]);
});

test("containment keeps the wider interval", () => {
  assert.deepEqual(mergeIntervals([[1, 10], [2, 3], [4, 8]]), [[1, 10]]);
});

test("disjoint intervals are left separate", () => {
  assert.deepEqual(mergeIntervals([[1, 2], [4, 5]]), [[1, 2], [4, 5]]);
});

test("duplicates collapse to one", () => {
  assert.deepEqual(mergeIntervals([[1, 2], [1, 2]]), [[1, 2]]);
});

test("mixed unsorted overlapping and disjoint", () => {
  assert.deepEqual(
    mergeIntervals([[8, 10], [1, 3], [2, 6], [15, 18]]),
    [[1, 6], [8, 10], [15, 18]],
  );
});

test("the input array is not mutated", () => {
  const input = [[3, 5], [1, 2]];
  mergeIntervals(input);
  assert.deepEqual(input, [[3, 5], [1, 2]]);
});
System prompt
You are an expert programmer. Solve the task exactly as specified. Output your solution as fenced code blocks using the required file name(s) and the exact function/type signatures requested. Prefer correctness; do not include prose outside code unless asked.
Proposed solution
```js
export function mergeIntervals(intervals) {
    if (intervals.length === 0) return [];

    // Create a copy of the intervals to avoid mutating the input
    const sortedIntervals = [...intervals].sort((a, b) => a[0] - b[0]);
    const merged = [sortedIntervals[0]];

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

        if (current[0] <= last[1]) {
            // Overlapping or touching intervals, merge them
            last[1] = Math.max(last[1], current[1]);
        } else {
            // Non-overlapping interval, add to the result
            merged.push(current);
        }
    }

    return merged;
}
```

Test output (stdout)

TAP version 13
# Subtest: empty input returns empty array
ok 1 - empty input returns empty array
  ---
  duration_ms: 0.592973
  type: 'test'
  ...
# Subtest: single interval is returned unchanged
ok 2 - single interval is returned unchanged
  ---
  duration_ms: 0.102371
  type: 'test'
  ...
# Subtest: unsorted disjoint input is sorted
ok 3 - unsorted disjoint input is sorted
  ---
  duration_ms: 0.06198
  type: 'test'
  ...
# Subtest: overlapping intervals merge
ok 4 - overlapping intervals merge
  ---
  duration_ms: 0.438152
  type: 'test'
  ...
# Subtest: touching endpoints merge
ok 5 - touching endpoints merge
  ---
  duration_ms: 0.06701
  type: 'test'
  ...
# Subtest: containment keeps the wider interval
ok 6 - containment keeps the wider interval
  ---
  duration_ms: 0.0502
  type: 'test'
  ...
# Subtest: disjoint intervals are left separate
ok 7 - disjoint intervals are left separate
  ---
  duration_ms: 0.0459
  type: 'test'
  ...
# Subtest: duplicates collapse to one
ok 8 - duplicates collapse to one
  ---
  duration_ms: 0.04032
  type: 'test'
  ...
# Subtest: mixed unsorted overlapping and disjoint
ok 9 - mixed unsorted overlapping and disjoint
  ---
  duration_ms: 0.059831
  type: 'test'
  ...
# Subtest: the input array is not mutated
ok 10 - the input array is not mutated
  ---
  duration_ms: 0.08578
  type: 'test'
  ...
1..10
# tests 10
# suites 0
# pass 10
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 27.100851