We need a function that merges closed intervals and returns the merged intervals in increasing order of their left endpoints. I will assume that every input pair already has its smaller endpoint first. The caller can pass an empty collection, a single interval, disjoint intervals, overlapping intervals, or intervals that merely share an endpoint. Because these are closed intervals, sharing an endpoint counts as overlap: the intervals [2, 6] and [6, 9] represent a continuous covered region and should become [2, 9]. The input should remain unchanged, so the implementation must avoid extending an inner list belonging to the caller.

The direct approach is to compare each interval with every other interval and repeatedly combine overlapping pairs. That can work, but the state is awkward. Combining two intervals can create an interval that overlaps something examined earlier, so a careless nested loop either misses a merge or needs another complete pass. A chain such as [1, 3], [7, 10], [3, 8] makes this problem visible: the third interval connects the other two even though the first pair looked separate. It is easier to arrange the intervals so that discovering a gap really closes the current component permanently.

Sorting by the left endpoint provides exactly that arrangement. Consider the leftmost interval that has not yet been handled. Its left endpoint will also be the left endpoint of its entire merged component, because nothing later begins earlier. We can keep a current pair, called start and end, and extend end while later intervals begin no farther right than end. A later interval contained inside the current one changes nothing. A later interval extending beyond the current right endpoint increases end. Once a later interval starts strictly after end, the current component cannot meet any future interval either, because their starting points are at least as large.

For a concrete trace, take [8, 12], [1, 4], [3, 7], [15, 17], and [12, 13]. After sorting, the first active component is [1, 4]. Reading [3, 7] extends its right edge to 7. Reading [8, 12] encounters a gap, so [1, 7] goes into the answer and [8, 12] becomes active. The next pair starts at 12, which is equal to the active end, so it extends that component to [8, 13]. Finally, [15, 17] follows another gap. We emit [8, 13], keep [15, 17], and append that last active pair after the loop finishes.

The important invariant is that the answer already contains complete, disjoint components, and the active pair describes the component containing every processed interval not yet emitted. Each emitted component ends strictly before the active component begins. Initially there are no emitted components and the first sorted interval is the active component, so the invariant holds. If an interval overlaps the active pair, taking the larger right endpoint preserves the union without changing its left endpoint. If it starts after the active end, the sorted order proves that the active pair is complete, so emitting it and starting another pair also preserves the invariant.

At the end, every input interval has been processed. The invariant then says that the emitted pairs together with the active pair represent the whole union. Appending the active pair produces the desired result. This final append matters: the loop only emits a component when another component begins, so the rightmost component otherwise has no event that would place it in the result. Empty input is handled before choosing a first pair, because there is no meaningful initial active interval in that case.

There are several details worth checking before writing the final function. Sorting a list of pairs without a key compares the left endpoints first and then the right endpoints on a tie. That ordering is suitable here. If two intervals have the same left endpoint, either can begin the active component; the maximum operation eventually keeps the larger right endpoint. An explicit key based on the first element would therefore also work. I will use ordinary pair sorting because it is concise and deterministic for the numeric endpoints described by the problem.

We should make fresh output lists instead of reusing sorted input pairs. Calling sorted creates a new outer list, but the elements inside it are still references to the original inner objects. An implementation that sets current = ordered[0] and assigns current[1] would mutate the caller's first interval. Keeping the endpoints in local variables avoids that issue. Each time we append [start, end], we construct a new pair. Even an input containing tuples will work, since the algorithm only reads its intervals and produces lists deliberately.

Negative endpoints require no special logic. For example, [-9, -4] and [-6, 2] merge because -6 is no greater than -4. Degenerate intervals such as [5, 5] also behave consistently: they represent a single point and merge with any closed interval containing that point. Several identical pairs collapse into a single pair because every duplicate overlaps the active component. A list that is already sorted follows the same path as any other input, and a reverse-sorted list is normalized by the sorting step before the scan.

The condition should be left <= end. Using left < end would instead treat touching intervals as separate, which would be appropriate for some half-open conventions but contradicts the closed-interval requirement here. Conversely, testing right >= start is unnecessary after sorting: the new interval starts no earlier than the active component, and its own endpoints are assumed valid. The one comparison against the active right endpoint is sufficient to decide whether a gap has appeared. We should keep the interpretation explicit in the docstring so callers do not need to infer it from a boundary comparison.

The cost of sorting dominates the running time. For n intervals, sorting uses O(n log n) comparisons and the scan performs O(n) work. The result may contain n pairs when everything is disjoint. The sorted copy also uses O(n) additional storage, which is the price of preserving the caller's ordering. If mutation were allowed, sorting the original outer list could avoid that copy, but changing the function's contract for that small saving would be surprising. This version favors a predictable input-preserving interface.

I can also check the invariant against an interval entirely inside the active pair. Suppose the active pair is [2, 20] and the next input is [4, 9]. The overlap branch leaves end equal to 20, so the larger region remains intact. Later, [19, 24] extends it to 24. Nothing in the logic assumes each overlapping interval makes progress, which is important because containment and duplicates are common in interval problems. A component grows only when the new right endpoint reaches farther; otherwise processing still advances to the next input.

The final code needs no repeated pairwise merges and no second cleanup pass. Its two state transitions correspond directly to the proof above, and the empty case is separated before initialization. Here is the complete function:

```python
def merge_intervals(intervals):
    """Merge closed intervals, preserving the supplied collection and its pairs."""
    ordered = sorted(intervals)
    if not ordered:
        return []

    start, end = ordered[0]
    merged = []
    for left, right in ordered[1:]:
        if left <= end:
            end = max(end, right)
        else:
            merged.append([start, end])
            start, end = left, right
    merged.append([start, end])
    return merged
```
