DSA Guide
Arrays

Container With Most Water

Maximise the area between two lines using converging two pointers, and understand why moving the shorter side is always safe

MediumTwo Pointers (converging)
ArrayTwo PointersGreedy
Problem #11

Problem Statement

You are given an array height where height[i] is the height of a vertical line drawn at position i. Pick two lines that, together with the x-axis, form a container. Return the maximum amount of water it can hold.

Input:  height = [1, 8, 6, 2, 5, 4, 8, 3, 7]
Output: 49
Why:    lines at index 1 (height 8) and index 8 (height 7)
        width  = 8 - 1 = 7
        height = min(8, 7) = 7
        area   = 7 × 7 = 49

The area formula

area = (right - left) × min(height[left], height[right])

Two things matter. The width is the distance between the lines. The usable height is the shorter of the two lines — water spills over the lower wall, so the taller line's extra height is wasted.

Intuition

Brute force checks all n × (n-1) / 2 pairs — O(n²).

To do better, start from the widest possible container: left = 0, right = n - 1. This maximises the width. Any other pair is narrower, so it can only win by being taller.

Now the crucial question: which pointer should move inward?

Always move the shorter line — here is the proof

Say height[left] < height[right]. Consider keeping left fixed and moving right inward:

  • The width strictly decreases (the pointers got closer).
  • The usable height is min(height[left], height[new right]), which can never exceed height[left] — it is capped by the short line, which did not change.

So every container that keeps the short line is narrower and no taller. All of them are worse. You can discard the short line entirely, and moving it is the only move that could ever help.

That argument is what makes the algorithm correct rather than just plausible. Each step permanently eliminates one line, so n steps suffice.

Real-world analogy

Two people hold a sheet between them to catch water. The sheet fills only up to the lower hand. Asking the taller person to step closer helps nobody — the water level is set by the lower hand. The only hope of improvement is to replace the lower hand.

Approach

Start at both ends

left = 0, right = n - 1 — the widest container possible.

Compute the area and keep the best

area = (right - left) × min(height[left], height[right]).

Move the pointer at the shorter line inward

If they tie, either move works — both lines are equally limiting, so both can be discarded.

Stop when the pointers meet

A container needs two distinct lines.

Watch it run

height = [1, 8, 6, 2, 5, 4, 8, 3, 7]
1
0
L
8
1
6
2
2
3
5
4
4
5
8
6
3
7
7
8
R
width8height1area8best8
Widest container: width 8, but the left line is only 1 tall → area 8. Left is shorter, so move L.
1 / 5
Each step throws away exactly one line that can never be part of a better answer.

Solution

def max_area(height: list[int]) -> int:
    """Largest area of water between any two vertical lines."""
    left, right = 0, len(height) - 1
    best = 0

    while left < right:
        # Water spills over the shorter wall, so it caps the height.
        area = (right - left) * min(height[left], height[right])
        best = max(best, area)

        # Moving the taller line can never help — see the proof above.
        if height[left] < height[right]:
            left += 1
        else:
            right -= 1

    return best

Complexity Analysis

Time Complexity

O(n)

Space Complexity

O(1)

  • Time — the pointers only ever move toward each other, so the loop runs at most n - 1 times. Brute force is O(n²): at n = 10⁵, that is 100,000 steps versus 5 billion.
  • Space — two indices and a running maximum.

Edge Cases

On this page