
Intervals in Practice: Solving the 'Merge Intervals' Problem

Merge Intervals is one of those problems that becomes much easier once we stop seeing it as lots of separate comparisons and start seeing it as a question about order. We are given a list of ranges such as [1, 3] or [2, 6], and we need to combine any overlaps so the final output contains only the merged spans.
The common first mistake is to compare every interval with every other interval and try to reason about all possible pairings. That gets messy quickly. The cleaner way in is to sort the intervals by start time and then walk through them once from left to right.
That is the whole pattern. Sort first, then merge as you go.
What the Problem is Really Asking
If the input is:
[[1, 3], [2, 6], [8, 10], [15, 18]]the output should be:
[[1, 6], [8, 10], [15, 18]]The first two intervals overlap, because the second starts before the first one has ended. So they belong to the same merged range.
What matters is not just whether intervals touch, but whether the next interval starts before the current merged interval finishes.
Why Sorting Changes the Whole Problem
Without sorting, any interval could overlap with any earlier one, so the comparisons become awkward.
Once the intervals are sorted by starting value, the picture becomes calmer:
- if the next interval overlaps with the current one, merge them
- if it does not, the current merged interval is finished and can be pushed to the result
That works because sorting guarantees we only need to compare each interval with the most recent merged range, not with everything seen so far.
A Practical TypeScript Solution
type Interval = [number, number];export const merge = (intervals: Interval[]): Interval[] => { if (intervals.length <= 1) { return intervals; } const sortedIntervals = [...intervals].sort((left, right) => { return left[0] - right[0]; }); const mergedIntervals: Interval[] = [sortedIntervals[0]]; for (let index = 1; index < sortedIntervals.length; index += 1) { const [currentStart, currentEnd] = sortedIntervals[index]; const lastMergedInterval = mergedIntervals[mergedIntervals.length - 1]; const [lastStart, lastEnd] = lastMergedInterval; if (currentStart <= lastEnd) { lastMergedInterval[0] = lastStart; lastMergedInterval[1] = Math.max(lastEnd, currentEnd); } else { mergedIntervals.push([currentStart, currentEnd]); } } return mergedIntervals;};Why Only the Last Merged Interval Matters
This is the part that makes the sorted approach feel trustworthy rather than lucky.
Because the intervals are ordered by start value, if the current interval overlaps at all, it can only overlap with the latest merged range. Any earlier range has already ended before that latest one, otherwise it would have been merged into it already.
That means we do not need to check:
- the current interval against every previous interval
We only need to check:
- the current interval against the most recent merged interval
That is why the algorithm stays linear after the sort.
Touching Intervals versus Separated Intervals
One detail worth being explicit about is the overlap test:
currentStart <= lastEndThat means intervals like [1, 4] and [4, 5] are treated as overlapping and merged into [1, 5].
That behaviour matches the usual version of the problem, but it is always worth checking the wording in related interval questions. Some variants treat touching ranges differently depending on whether the endpoints are inclusive or exclusive.
Why Copying Before Sorting is Sensible
The solution uses:
const sortedIntervals = [...intervals].sort(...)That copy is worth keeping because sort() mutates the array in place. In an interview, mutating the input may be fine if it is allowed. In clearer application code, though, making the mutation explicit or avoiding it entirely is often easier to trust.
This is a small detail, but interval questions are a good place to keep that discipline because sorting is so central to the pattern.
Common Mistakes
Skipping the Initial Sort
Without sorting, the one‑pass merge logic is not safe. The ordering step is not a convenience. It is the reason the whole approach works.
Appending the Current Interval Too Early
If an interval overlaps with the most recent merged range, we should extend that existing range, not push a new one.
Using < instead of <= without thinking
That changes whether touching intervals merge. The comparison should match the problem definition.
Complexity and Why the Pattern Appears so Often
The sort costs O(n log n). The merge pass afterwards is O(n), so the overall complexity is O(n log n).
That is usually the right answer for interval problems because sorting gives us the order needed to compress a potentially messy overlap structure into one straightforward pass.
Wrapping up
Merge Intervals is a strong pattern problem because the code is simple once the ordering is right. The real skill is recognising that sorting changes the shape of the whole task from "check everything against everything" to "merge the current interval into the latest valid range".
Key Takeaways
- Sort intervals by start value before trying to merge anything.
- After sorting, each interval only needs to be compared with the last merged range.
- The overlap rule is the small detail that determines the final behaviour.
This is one of those problems where a single good first step does most of the heavy lifting. Sort the intervals, and the rest becomes much more manageable.
Related Articles

Understanding the CSS :where() Function. Understanding the CSS

Optimising Performance in React with useMemo and useCallback. Optimising Performance in React with
useMemoanduseCallback
Backtracking Decision Trees: Solving 'Combination Sum'. Backtracking Decision Trees: Solving 'Combination Sum'

LeetCode: The 'Trapping Rain Water' Problem with Two‑Pointer Approach. LeetCode: The 'Trapping Rain Water' Problem with Two‑Pointer Approach

Understanding prototype.apply() in JavaScript. Understanding
prototype.apply()in JavaScript
Promises in JavaScript: An Introduction. Promises in JavaScript: An Introduction

Finding the Difference Between Two Strings in JavaScript. Finding the Difference Between Two Strings in JavaScript

Container Queries in CSS. Container Queries in CSS

Higher‑Order Functions in JavaScript. Higher‑Order Functions in JavaScript

Fast and Slow Pointers: Solving the 'Linked List Cycle' Problem. Fast and Slow Pointers: Solving the 'Linked List Cycle' Problem

Static Generation vs. Server‑Side Rendering in Next.js. Static Generation vs. Server‑Side Rendering in Next.js

Creating Custom Vue Directives for Enhanced Functionality. Creating Custom Vue Directives for Enhanced Functionality