Home DSA Container With Most Water: 5 Brilliant Moves That Win Fast
Intermediate 3 min · September 07, 2026

Container With Most Water: 5 Brilliant Moves That Win Fast

Container With Most Water solved in O(n) with two pointers.

N
Naren Founder & Principal Engineer

20+ years shipping performance-critical code where algorithms decide the bill. Notes here come from systems that actually shipped.

Follow
Production
production tested
September 22, 2026
last updated
1,799
articles · all by Naren
Before you start⏱ 15 min
  • Two-pointer convergence basics
  • Min/max area reasoning
  • Big-O time and space analysis
 ● Production Incident 🔎 Debug Guide
Quick Answer
  • Container With Most Water asks for the max min(h[i],h[j]) × (j-i) over all line pairs (e.g. [1,8,6,2,5,4,8,3,7] gives 49)
  • Optimal approach: pointers at both ends, always advance the shorter line — O(n) time, O(1) space
  • Key trick: the shorter line caps the area, so retiring it (and only it) can never discard the optimum
  • Asked at Amazon, Meta, and Google in most two-pointer rounds — the standard greedy-proof filter
✦ Definition~90s read
What is Container With Most Water?

Container With Most Water is LeetCode 11, a Medium array problem and the canonical greedy two-pointer interview question. Given up to 10^5 line heights, you choose two lines maximizing the shorter height times the index gap. It is asked at Amazon, Meta, and Google because the code is eight lines while the correctness proof separates memorizers from reasoners.

Picture a row of fence posts of different heights.

The solution converges pointers from both ends, always retiring the shorter line: every remaining pair with that line is narrower and no taller, so no optimum is lost. That exchange argument yields O(n) time and O(1) space, both optimal. The shorter-side reasoning transfers directly to Trapping Rain Water, making this problem the gateway to the harder rain-water family.

Plain-English First

Picture a row of fence posts of different heights. Pick any two posts — water poured between them rises only to the shorter post, and the amount held equals that height times the gap between posts. Start with the two outermost posts and note the volume. Now, drop the shorter of the two and step inward to the next post; dropping the taller one could never help, because the shorter post was already the limit. Repeat until the posts meet. The biggest volume you noted along the way is the answer. Each post gets dropped once, so you check the whole row in a single sweep.

Two lines, one container, max water. Sounds trivial. It filters ruthlessly.

The obvious code checks every pair. That's O(n^2) — dead past 10,000 lines. You'll code it fast, feel good for a minute, then watch the stress test stall.

The fix is two pointers at the ends, always retiring the shorter line. Each step kills one hopeless candidate. That's O(n) time, O(1) space, eight lines. The code's easy; the proof's the interview. Learn both.

Area Is Capped by the Shorter Line, Never the Taller One

Given heights array h with n vertical lines, pick i < j maximizing min(h[i],h[j]) × (j - i). Water rises to the shorter line; width is the index gap. Example [1,8,6,2,5,4,8,3,7] gives 49: lines 8 (index 1) and 7 (index 8) hold min(8,7) = 7 over gap 8 - 1 = 7, so 7 x 7 = 49.

Constraints reach n = 10^5, heights to 10^4. O(n^2) pairs (~5×10^9) are hopeless. Answer fits 32-bit but Python ints make that moot. Smoke test: [1,1] → 1, [] → 0.

Walk [1,8,6,2,5,4,8,3,7]: (1,7) area 8 → move left (1 shorter). (8,7) area 7×7=49 → move right (7 shorter). (8,3) 3×6=18 → move right. (8,8) 8×5=40 → either; move left. (6,8) 6×4=24 → move left... best stays 49. One sweep, eight steps.

📊 Production Insight
Compute the example's 49 by hand (7 × 7) before coding. Candidates who can't name the winning pair write moves that discard it.
🎯 Key Takeaway
Area = shorter height × index gap; [1,8,6,2,5,4,8,3,7] → 49 — trace the sweep before coding.

Testing Every Pair of Lines Is O(n^2) on 10^5 Heights

Brute force: every pair (i,j), compute min × gap, keep max. O(n^2) time, O(1) space. At n = 10^5 that's ~5×10^9 area computations — minutes to hours. Even n = 10^4 (50M pairs) misses the limit in Python.

No hash-map rescue exists: area couples values with positions, so lookup tricks don't apply. Sorting destroys indices. The pair structure forces either quadratic enumeration or the greedy insight — there is no middle ground worth coding.

Interview play: 'All pairs is O(n^2) — dead at 10^5. The shorter line caps area, so I'll converge from the ends retiring it.' Two sentences, then code. Anything longer wastes the clock.

📊 Production Insight
Name why shortcuts fail (sorting kills indices, hashing can't couple pairs). Ruling things out fast reads as senior judgment.
🎯 Key Takeaway
All-pairs costs O(n^2) with no hash shortcut — position-coupled problems demand the greedy insight.

Two Pointers: Always Move the Shorter Wall Inward

Place left = 0, right = n-1, best = 0. While left < right: best = max(best, min(h[left],h[right]) × (right-left)); if h[left] < h[right]: left += 1 else: right -= 1. (Ties: either side; moving right is conventional.)

Proof sketch: let the shorter side be h[left] ≤ h[right]. Every pair (left, k) with k < right is narrower than the current width and capped by the same height h[left] — so none can beat the best achievable with left fixed, which is at most h[left] × (right-left), already recorded. Hence left can be retired with no optimum lost. Symmetric for the right. Each step retires one line: n-1 steps, O(1) each → O(n) time, O(1) space. Every line is read; O(n) is optimal.

Equal heights: retiring either side is safe by the same argument (both bounds equal the recorded area's cap). Optional skip of equal runs saves constant steps.

📊 Production Insight
Deliver the domination sentence verbatim: 'every remaining pair with this line is narrower and no taller.' That's the proof; everything else is commentary.
🎯 Key Takeaway
Retire the shorter line: all its remaining pairs are width-and-height dominated — O(n) optimal.

The Two-Pointer Solution in Full Python

The code above is the complete LeetCode submission. Note the area is measured before the pointer moves, using the pre-move width — trace [1,1]: width 1, heights tie, area = 1×1 = 1, right retreats, loop ends, returns 1. Trace the big example: best climbs 8 → 49 → stays, returns 49.

Complexity: O(n) time, O(1) space. No imports, no lists, no slices — safe past n = 10^5.

solution.pyPYTHON
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Solution:
    def maxArea(self, height: list[int]) -> int:
        left, right = 0, len(height) - 1
        best = 0
        while left < right:
            width = right - left
            if height[left] < height[right]:
                area = height[left] * width
                left += 1
            else:
                area = height[right] * width
                right -= 1
            if area > best:
                best = area
        return best
⚠ The Single Rule of This Problem
Move the SHORTER pointer. Every time. Ties go either way — pick one rule and stay consistent.
📊 Production Insight
Run [1,1] → 1 live the moment the code is written. It validates formula, ordering, and loop bounds in a single second.
🎯 Key Takeaway
Measure-then-move, shorter-side-retires — verify on [1,1] and the 9-element example.

Two Lines Only, Equal Heights and Zero-Height Walls

Empty and single-element return 0 (loop never runs). Two elements return min × 1. All-equal heights return 1 × (n-1) — the widest pair. Strictly decreasing [5,4,3,2,1] resolves to 6 (5×... pairs: (5,1) gap 4 → 4; (5,2) gap 3 → 6; best 6). Strictly increasing mirrors it.

The disguised-quadratic trap: max(height[left:right]) or any slice inside the loop. Each slice costs O(n); n slices restore O(n^2). Grep the loop body — it must contain only indexing, arithmetic, and pointer moves.

📊 Production Insight
Monotonic inputs are the silent killers: a reversed move rule still scores decently on them. Only the classic example (49) exposes the rule flip.
🎯 Key Takeaway
Test [], [5], [1,1], flat, monotonic — five inputs, every branch covered.

Why a Single Pass From Both Ends Is Optimal

Time O(n): n-1 iterations, O(1) work each. Space O(1): three integers. Both optimal — each height must be read once (time floor), and only pointers plus best need storage (space floor).

Whiteboard closer: 'Converge from the ends, retire the shorter line, measure before moving.' Then bridge: 'The same shorter-side logic with running maxima gives Trapping Rain Water.' That bridge answers the follow-up before it's asked.

📊 Production Insight
Close by naming the Trapping Rain Water bridge. Interviewers promote candidates who file problems into families, not isolated tricks.
🎯 Key Takeaway
O(n) time is a read-every-line lower bound; O(1) space needs only pointers and best.
● Production incidentPOST-MORTEMseverity: high

The 24-Minute Backwards Convergence That Scored 40 of 49

Symptom
The example returned 40 instead of 49 and [1,1] returned 0, with 15 minutes left. The candidate 'verified' convergence but never verified which pointer moved.
Assumption
The candidate assumed moving either pointer was fine 'as long as they converge' and never reasoned about which side caps the area. They believed convergence alone guaranteed correctness.
Root cause
The code advanced the taller pointer each step, discarding the (8,7) optimum pair early and scoring 40. A second bug — best updated after the move — also zeroed two-element inputs.
Fix
With 11 minutes left the interviewer asked which line limits the volume. The candidate answered 'the shorter one', flipped the move rule, and passed all tests with 4 minutes left — graded hire-leaning for a clean, narrated recovery.
Key lesson
  • State the bound before coding: area is capped by the shorter line, so only its pointer moves.
  • Measure first, move second — ordering bugs hide in two-element tests, so run [1,1] immediately.
Production debug guideFour defect shapes and the exact check that exposes each one.4 entries
Symptom · 01
Example returns less than 49
Fix
Trace [1,8,6,2,5,4,8,3,7] fully: best must hit 49 at the (8,7) pair with gap 8. If your code moves the taller pointer anywhere, flip the rule to shorter-side-moves and retrace.
Symptom · 02
All answers systematically too high or too low
Fix
Isolate the formula line and unit-check [1,1] → 1 and [4,3,2,1,4] → 16. Confirm min() (not max) and gap (right - left) with no minus-one. Fix and rerun.
Symptom · 03
[1,1] returns 0 or short inputs crash
Fix
Move the best-update above the pointer move and initialize best = 0. Retest [1,1] → 1, [] → 0, [5] → 0. The first pair must be measured before anything moves.
Symptom · 04
Timeouts on large inputs despite two pointers
Fix
Time 100,000 random heights — must finish well under a second. Grep the loop for max(, min(, sorted(, or [:] slices; hoist or delete every one.
Container Approaches Compared
ApproachTimeSpaceVerdict
Brute force (all pairs)O(n^2)O(1)Dead past n = 10,000
Divide and conquerO(n log n)O(log n)Correct, but complex and pointless here
Two pointers (move shorter)O(n)O(1)Best: optimal, eight lines
Two pointers + equal-height skipO(n)O(1)Same class, fewer steps in practice

Key takeaways

1
Container With Most Water maximizes min(h[left],h[right]) × (right-left); converging pointers solve it in O(n)/O(1).
2
Always move the shorter line
moving the taller one can never improve the area.
3
Measure area before moving pointers and seed best at 0 for short-input safety.
4
Never slice or re-scan inside the loop; that smuggles O(n^2) back in.
5
The shorter-side argument transfers directly to Trapping Rain Water follow-ups.

Common mistakes to avoid

4 patterns
×

Moving the taller pointer instead of the shorter one

Symptom
[1,8,6,2,5,4,8,3,7] returns under 49 — often 40 or less. The true optimum (8 and 7 at distance 8) is skipped when tall lines retreat early.
Fix
Always advance the pointer at the shorter line. Compute area before moving, then move exactly one pointer per step. The shorter side is the only one whose move can possibly improve the bound.
×

Using max height or width minus one in the area formula

Symptom
Every answer overshoots on tall-outer inputs or undershoots uniformly. [1,1] must give exactly 1 — a perfect one-line smoke test.
Fix
Use min(height[left], height[right]) * (right - left) with the raw index gap. Never subtract 1, never use max. Recompute from scratch each step.
×

Updating the best area after moving, or skipping the initial pair

Symptom
Two-element inputs like [1,1] return 0. The widest pair (often the answer) is never measured when the update sits in the wrong place.
Fix
Update best before moving any pointer, on every iteration including the first. Initialize best to 0 so empty and single-element inputs return 0 naturally.
×

Re-scanning or slicing inside the loop (disguised O(n^2))

Symptom
Timeouts past n = 50,000 despite 'two pointers'. Any max(height[left:right]) or slice inside the loop restores quadratic cost.
Fix
Keep the loop as while left < right with one pointer move per pass — pure O(n). The only legal skip is jumping equal heights; never add an inner scan or max() over slices.
INTERVIEW PREP · PRACTICE MODE

Interview Questions on This Topic

Q01SENIOR
Prove the greedy pointer choice never discards the optimum.
Q02SENIOR
How does this connect to Trapping Rain Water?
Q03SENIOR
Can you extend this to k lines, or beat O(n)?
Q01 of 03SENIOR

Prove the greedy pointer choice never discards the optimum.

ANSWER
Area is capped by the shorter line. Advancing the taller line shrinks width with no possible height gain, so no optimum is lost by keeping it. Advancing the shorter line is the only move that can raise the cap. Each step eliminates one line permanently; n-1 steps cover all candidates that could beat the best.
FAQ · 5 QUESTIONS

Frequently Asked Questions

01
Why is it safe to move the shorter line?
02
What if both lines are equal height?
03
Is this a greedy algorithm?
04
Can I sort the heights first?
05
How do I return the actual best pair, not just the area?
N
Naren Founder & Principal Engineer

20+ years shipping performance-critical code where algorithms decide the bill. Notes here come from systems that actually shipped.

Follow
Verified
production tested
September 22, 2026
last updated
1,799
articles · all by Naren
🔥

That's Arrays. Mark it forged?

3 min read · try the examples if you haven't

Previous
Three Sum Triplet Problem
3 / 3 · Arrays
Next
Climbing Stairs Dynamic Programming