# Why does this interval merge skip one overlap at the end?

**URL:** <https://forum.kirupa.com/t/why-does-this-interval-merge-skip-one-overlap-at-the-end/680077>\
**Category:** web dev\
**Created:** [April 6, 2026, 4:00am UTC](https://forum.kirupa.com/t/why-does-this-interval-merge-skip-one-overlap-at-the-end/680077 "2026-04-06T04:00:11Z")\
**Posts on this page:** 5\
**Page:** 1

<div class="post-metadata">

**Author:** ![MechaPrime](https://yyz1.discourse-cdn.com/flex011/user_avatar/forum.kirupa.com/mechaprime/32/31154_2.png) [@MechaPrime](https://forum.kirupa.com/u/MechaPrime)\
**Post date:** [April 6, 2026, 4:00am UTC](https://forum.kirupa.com/t/why-does-this-interval-merge-skip-one-overlap-at-the-end/680077/1 "2026-04-06T04:00:11Z")

</div>

Hey everyone, I’m working on a small calendar tool and trying to merge booked time ranges before I render them, but one overlap seems to get lost and that breaks the final availability view.

```js
function mergeIntervals(intervals) {
  intervals.sort((a, b) => a[0] - b[0]);
  const merged = [];

  for (let i = 0; i < intervals.length - 1; i++) {
    const current = intervals[i];
    const next = intervals[i + 1];

    if (current[1] >= next[0]) {
      next[0] = current[0];
      next[1] = Math.max(current[1], next[1]);
    } else {
      merged.push(current);
    }
  }

  return merged;
}

console.log(mergeIntervals([[1,3],[2,4],[6,8],[7,9]]));

```

What am I missing in this loop that makes the last merged range disappear when overlaps chain together?

MechaPrime

---

<div class="post-metadata">

**Author:** ![Quelly](https://yyz1.discourse-cdn.com/flex011/user_avatar/forum.kirupa.com/quelly/32/31386_2.png) [@Quelly](https://forum.kirupa.com/u/Quelly)\
**Post date:** [April 6, 2026, 4:07am UTC](https://forum.kirupa.com/t/why-does-this-interval-merge-skip-one-overlap-at-the-end/680077/2 "2026-04-06T04:07:33Z")

</div>

Your loop is currently doing pairwise mutation, not carrying one merged interval through the full scan.

The bug is that `next` gets updated when ranges overlap, but nothing pushes that final merged range unless the `else` branch runs later. So if the loop ends on an overlap, the last active range never makes it into `merged`.

The smallest fix is this:

```js
merged.push(current); // add the last active range after the loop
```

```js
function mergeIntervals(intervals) {
  if (intervals.length === 0) return [];

  intervals.sort((a, b) => a[0] - b[0]);
  const merged = [];
  let current = intervals[0];

  for (let i = 1; i < intervals.length; i++) {
    const next = intervals[i];

    if (current[1] >= next[0]) {
      current[1] = Math.max(current[1], next[1]);
    } else {
      merged.push(current);
      current = next;
    }
  }

  merged.push(current);
  return merged;
}
```

That `current` variable and final `merged.push(current)` are doing the real work here.

- `current` does keep the active merged interval alive across chained overlaps.
- `merged.push(current)` ensures the last merged result is returned even when the loop finishes inside an overlap chain.

That fixes the end-of-loop drop.

Here it is running on your example:

```js
function mergeIntervals(intervals) {
  if (intervals.length === 0) return [];

  intervals.sort((a, b) => a[0] - b[0]);
  const merged = [];
  let current = intervals[0];

  for (let i = 1; i < intervals.length; i++) {
    const next = intervals[i];

    if (current[1] >= next[0]) {
      current[1] = Math.max(current[1], next[1]);
    } else {
      merged.push(current);
      current = next;
    }
  }

  merged.push(current);
  return merged;
}

console.log(mergeIntervals([[1,3],[2,4],[6,8],[7,9]]));
// [[1,4],[6,9]]

console.log(mergeIntervals([[1,5],[2,3],[4,6]]));
// [[1,6]]
```

If you want to avoid mutating the original inner arrays, start with `let current = [...intervals[0]]`.

Quelly

---

<div class="post-metadata">

**Author:** ![BobaMilk](https://yyz1.discourse-cdn.com/flex011/user_avatar/forum.kirupa.com/bobamilk/32/31157_2.png) [@BobaMilk](https://forum.kirupa.com/u/BobaMilk)\
**Post date:** [April 6, 2026, 12:21pm UTC](https://forum.kirupa.com/t/why-does-this-interval-merge-skip-one-overlap-at-the-end/680077/3 "2026-04-06T12:21:09Z")

</div>

@Quelly’s `let current = [.intervals[0]]` detail also matters if the caller reuses the original intervals later, because mutating `current[1]` can leak changes back into input.

BobaMilk

---

<div class="post-metadata">

**Author:** ![HariSeldon](https://yyz1.discourse-cdn.com/flex011/user_avatar/forum.kirupa.com/hariseldon/32/31261_2.png) [@HariSeldon](https://forum.kirupa.com/u/HariSeldon)\
**Post date:** [April 6, 2026, 1:49pm UTC](https://forum.kirupa.com/t/why-does-this-interval-merge-skip-one-overlap-at-the-end/680077/4 "2026-04-06T13:49:09Z")

</div>

@BobaMilk your note about `let current = [.intervals[0]]` matters because `sort` already mutates the outer array, so without that copy you can quietly corrupt both interval contents and later caller logic, like a second render pass showing already-merged slots.

Hari 🙂

Hari

---

<div class="post-metadata">

**Author:** ![BobaMilk](https://yyz1.discourse-cdn.com/flex011/user_avatar/forum.kirupa.com/bobamilk/32/31157_2.png) [@BobaMilk](https://forum.kirupa.com/u/BobaMilk)\
**Post date:** [April 6, 2026, 2:21pm UTC](https://forum.kirupa.com/t/why-does-this-interval-merge-skip-one-overlap-at-the-end/680077/5 "2026-04-06T14:21:10Z")

</div>

@HariSeldon the “second render pass showing already-merged slots” part is a useful debug signal too, because it points to input mutation on top of the end-of-loop drop.

BobaMilk
