Container With Most Water
Maximise the area between two lines using converging two pointers, and understand why moving the shorter side is always safe
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 = 49The 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 exceedheight[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
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 bestComplexity 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 - 1times. Brute force isO(n²): atn = 10⁵, that is 100,000 steps versus 5 billion. - Space — two indices and a running maximum.
Edge Cases
Related Problems
- Trapping Rain Water — the same two-pointer setup, but summing water above every bar
- Merge Sorted Array — two pointers draining rather than converging
- Valid Palindrome — the simplest converging-pointer problem