Understand
30-second explanation
Build the interview-ready mental model before comparing implementations.
Maximize area = (distance between two lines) × (height of the shorter line). The interview-grade answer is two pointers starting at both ends: compute the area, then move the pointer at the shorter wall inward — moving the taller one can never help. O(n) time, O(1) space, beating the O(n²) all-pairs brute force.
Interview contextWhat is testedWhere candidates slipHow to stand outExplore interview contextHide interview context
What's being tested
Whether you can prove that moving the shorter wall is always safe and turn an O(n²) pair search into an O(n) two-pointer sweep with a greedy exchange argument.
Where candidates slip
Moving the taller wall (or both), or confusing this with trapping rain water and trying to sum water over every index.
How to stand out
State brute force, then justify the two-pointer move out loud: 'the area is capped by the shorter wall, so keeping it can never beat moving it — therefore discard it.' That correctness argument is the whole point.
Choose
Compare the available approaches
Use constraints and trade-offs to select the route you can defend in an interview.
Interactive decision treeInteractiveChoose the right approachFirst decision previewMax area between two vertical linesCan you argue one pointer is safe to move?NoYes (move shorter wall)Explore the full treeCollapse the tree
Learn & trace
Build each solution step by step
Move from the core idea to a visual trace, implementation, proof, and edge cases.
Approach 1 of 2
Brute Force — Check Every Pair
1Hint 1Explore hintHide hint
The most direct reading: the answer is some pair of lines. How many pairs are there?
2Hint 2Explore hintHide hint
Try every pair (i, j) with i < j. The area is (j - i) * min(height[i], height[j]).
3Hint 3Explore hintHide hint
Track the maximum across all pairs. It's correct but quadratic — your cue to find structure that lets you skip pairs.
A · Core idea
Consider every unordered pair of lines (i, j) and compute the rectangle they enclose: width j - i times the shorter height min(height[i], height[j]). Keep the largest area seen.
Why this is the natural first attempt. The answer is literally 'the best pair of lines', so enumerating all pairs is the obvious correct baseline. Stating it shows you understand the area formula before optimizing.
Why we don't ship this. With n up to 10⁵, n²/2 is about 5 billion pairs — far too slow. The inefficiency is that we re-examine pairs that can't possibly beat what we've found. The two-pointer method exploits a property that lets us discard a whole wall at a time instead of one pair at a time.
B · Visual dry run
Interactive dry run
Select a frame to connect the decision with its visual state.
height = [1, 2, 1](0,1)
width 1 × min(1,2)=1
best = 12C · Code with teaching comments
Reference implementation previewpublic int maxArea(int[] height) { int best = 0; int n = height.length; for (int i = 0; i < n; i++) { for (int j = i + 1; j < n; j++) {Explore the full codeHide the full code
D · Why it works and complexityO(n²) timeO(1) spaceOpen the invariant and proofExplore the proofHide the proof
Brute force treats all pairs as equally worth checking. The key observation it misses: starting from the widest pair and shrinking, the only way a narrower container can beat a wider one is by being taller — so the shorter wall is the thing holding us back, and it's safe to abandon it.
ComplexityO(n²) time, O(1) space — C(n,2) ≈ n²/2 pairs, each evaluated in O(1). No extra storage beyond the running maximum → O(1) space.
E · Edge cases and pitfalls3Using max instead of min for the height — water can't sit higher than the shorter wall.Explore edge casesHide edge cases
Pitfalls
- Using max instead of min for the height — water can't sit higher than the shorter wall.
- Off-by-one on width (using j - i + 1) — the container width is the index distance j - i.
- Shipping O(n²) when n = 10⁵ — it will time out.
Approach 2 of 2 · Optimal
Two Pointers — Move the Shorter Wall (Optimal)
1Hint 1Explore hintHide hint
Start with the widest possible container — one pointer at each end. Any other container is narrower, so to beat this you need more height.
2Hint 2Explore hintHide hint
The area is capped by the shorter wall. If you move the taller wall inward, width shrinks and the height is still capped by that same shorter wall — area can't grow.
3Hint 3Explore hintHide hint
So always move the pointer at the shorter wall. That's the only move that could expose a taller wall and possibly increase the area.
A · Core idea
Place l at index 0 and r at index n-1 — the widest container possible. Compute its area, update the best, then advance the pointer sitting at the shorter wall. Repeat until the pointers meet.
Why starting widest is the right frame. Width is maximal at the ends and can only shrink as pointers move inward. So every future container is narrower; the only thing that can make a narrower container win is a taller limiting wall. That reframes the search from 'try all pairs' to 'try to find a taller wall before width costs too much.'
Why moving the shorter wall is provably safe — the exchange argument. Suppose height[l] < height[r]. The current area is (r − l) × height[l]. Consider keeping l fixed and moving r to any r' < r. The width r' − l is strictly smaller, and the height is still at most height[l] (the shorter wall, unchanged). So every container that keeps the short wall l is no better than the one we just measured. We can therefore discard l forever and move it inward — we lose nothing, and only by doing so might we find a wall taller than height[l]. The symmetric argument holds when the right wall is shorter.
Why this finds the global optimum. Each pointer move permanently eliminates one wall as a left/right endpoint, but only after we've recorded the best container that wall could ever participate in. Since no discarded option could have beaten what we measured, the maximum we track is the true maximum.
Why O(1) space and one pass. Two indices and a running maximum — nothing scales with input size, and each element is passed at most once.
B · Visual dry run
Interactive dry run
Select a frame to connect the decision with its visual state.
height = [1, 8, 6, 2, 5, 4, 8, 3, 7]Array state
l=0, r=8
area = 8 × min(1,7) = 8; left shorter → l++
49C · Code with teaching comments
D · Why it works and complexityO(n) timeO(1) spaceOpen the invariant and proofExplore the proofHide the proof
The whole algorithm rests on one exchange argument: the shorter wall caps the area, and any container still using that shorter wall is narrower and therefore no better — so abandon it. Spotting which endpoint is 'safe to discard' is the transferable skill; it turns many O(n²) pair searches into O(n) sweeps.
ComplexityO(n) time, O(1) space — Each step moves one pointer inward and they never move apart, so the pointers meet after at most n steps → O(n) time. Only two indices and a running max are stored → O(1) space.
E · Edge cases and pitfalls6height = [1, 1] — Only one pair: width 1 × min(1,1)=1 → returns 1.Explore edge casesHide edge cases
Edge cases handled
height = [1, 1]Only one pair: width 1 × min(1,1)=1 → returns 1.height = [0, 2, 0]Any container touching a 0 wall has area 0; the best is width 2 × min(0,0)=0 → 0.height = [2, 3, 4, 5, 18, 17, 6]The huge inner bars are reachable because moving the shorter wall keeps exploring inward; tracks the true max.
Pitfalls
- Moving the taller wall — width shrinks while the cap stays the same, so you can miss the optimum.
- Recomputing with max instead of min for height.
- Confusing this with Trapping Rain Water and trying to accumulate water over every index.
Practice
Try the solution yourself
Edit the optimal implementation, test another input, and observe the result.
Editable code previewpublic int maxArea(int[] height) { int l = 0, r = height.length - 1; int best = 0; while (l < r) { int area = (r - l) * Math.min(height[l], height[r]);Explore the playgroundClose the playground
Avoid mistakes
Recognize the interview traps
Understand why common attempts fail before reviewing the corrected implementation.
1Moving the wrong (taller) wall
If the right wall is taller and you move it, width shrinks but the height is still capped by the unchanged shorter left wall — you can only lose area and may skip the optimum.
Explore the code comparisonHide the code comparison
Moving the wrong (taller) wall
If the right wall is taller and you move it, width shrinks but the height is still capped by the unchanged shorter left wall — you can only lose area and may skip the optimum.
Don't write this
if height[l] < height[r]:
r -= 1 # moving the taller wall
else:
l += 1Write this instead
if height[l] < height[r]:
l += 1 # discard the shorter wall
else:
r -= 12Using max instead of min for the height
Water spills over the lower wall, so the usable height is the shorter of the two lines, not the taller.
Explore the code comparisonHide the code comparison
Using max instead of min for the height
Water spills over the lower wall, so the usable height is the shorter of the two lines, not the taller.
Don't write this
int area = (r - l) * Math.max(height[l], height[r]);Write this instead
int area = (r - l) * Math.min(height[l], height[r]);Revise
Carry the pattern into the interview
Pattern to remember
Two pointers from both ends, move the shorter wall
Code pattern to memorize
l, r = 0, n-1
best = 0
while l < r:
area = (r - l) * min(h[l], h[r])
best = max(best, area)
if h[l] < h[r]: l += 1
else: r -= 1
return bestRules to remember
- 1Area is bounded by the shorter of the two walls × the width between them.
- 2Always move the pointer at the shorter wall — moving the taller one can only shrink or tie the area.
- 3Start as wide as possible (both ends); width only decreases, so you trade width for a chance at more height.
- 4On a tie (
h[l] == h[r]), moving either is fine — the area can't improve by keeping both. - 5This is not the same as trapping rain water — here you pick exactly two lines, not sum trapped water.
Use this pattern when
- Maximize/minimize a quantity defined by a pair of endpoints
- The metric depends on width and the min/max of two endpoint values
- You can argue one pointer is always safe to move (a greedy exchange argument)
- Brute force is all-pairs O(n²) and you want O(n)
Choose another pattern when
- You must sum a contribution across all positions → that's Trapping Rain Water, not this
- The pair must satisfy a sum/target rather than maximize area → Two Sum family
- Order of elements can change → sorting problems, not fixed-position lines
Interview takeaway
When a quantity depends on two endpoints and the binding constraint is the smaller one, two pointers with a 'move the limiting side' rule finds the optimum in one pass. The proof that discarding the shorter wall loses nothing is the insight interviewers want to hear.
Continue
Transfer the pattern to another problem
Use a direct variation or pattern sibling to prove the idea is reusable.
Transferable patternTwo pointers with a greedy 'move the limiting endpoint' rule. Reach for it when a value depends on two endpoints and you can prove one side is always safe to discard. Cousin of Trapping Rain Water and 3Sum.
Practice queueDirect variations and pattern siblings
Direct follow-ups
- Trapping Rain WaterSum water trapped above every bar — also two pointers, but track leftMax/rightMax instead of a single pair.
- 3SumAnother two-pointer sweep, but on a sorted array hunting for a target sum.
- Largest Rectangle in HistogramComing soonMaximize a rectangle area too, but the bars between matter — needs a monotonic stack.