11. Container With Most Water - Detailed Explanation

Problem statement

You are given an array of non-negative integers representing heights of vertical lines drawn on an x-axis. Each element height[i] is the height of a vertical line at horizontal position i. Together with the x-axis, any two of these lines form a container for holding water.

The task is to find the two lines that can trap the most water when used as the sides of a container. The container must stay upright, so you cannot tilt it. Put another way, find indices i and j such that the area between the lines at i and j is maximized.

The area of water contained is determined by the width between the lines and the height of the shorter line:

  • Width = distance between the two lines (|j - i|).
  • Height = minimum of the two line heights (min(height[i], height[j])).

Area = width * height. We need to maximize this area.

Constraints:

  • 2 <= n <= 10^5 (number of lines).
  • 0 <= height[i] <= 10^4 (height of each line).

These constraints mean an efficient solution is required. A brute-force approach is O(n²), which would be too slow at the upper limits.

Example 1:

  • Input: height = [1,8,6,2,5,4,8,3,7].
  • Output: 49.
  • Explanation: The best container is formed by the lines of height 8 and 7, at positions 2 and 9 counting from one, or indices 1 and 8 counting from zero. The distance between these lines is 7 and the shorter height is 7, so the area = 7 * 7 = 49.

Example 2:

  • Input: height = [1,1].
  • Output: 1.
  • Explanation: There are only two lines. The width between them is 1 and the shorter height is 1, so the area = 1 * 1 = 1.

These examples illustrate how the container area is computed and what the maximum area looks like.

This question is an instance of the two pointers pattern. Two indices start at opposite ends of an array and walk toward each other, and every step discards a candidate that can no longer improve the answer. Design Gurus teaches this exact problem in the Container With Most Water lesson of Grokking the Coding Interview. Two pointers also sits near the top of the most common LeetCode patterns, so the reasoning below carries into many other questions.

Hints

  • Use the formula: The area is (distance between lines) * min(height[left], height[right]). The real challenge is choosing the two lines that maximize this product of width and minimum height.

  • Brute force thought: One straightforward approach considers every possible pair of lines and computes the area for each. That guarantees the maximum, but it costs O(n²) comparisons. Think about why you might not need to check all pairs.

  • Two-pointer insight: Start with the widest container, the first and last line, and record its area. To find a larger area you need a taller limiting line or a wider span. The width is already as large as it can get at the ends, so only a taller line can help. Move the pointer at the shorter line inward. The current area is capped by the shorter line, and moving the taller line inward would cut the width while leaving that cap untouched. Moving the shorter side inward may instead expose a taller line and raise the cap.

  • Think greedily: Each pointer move drops one line from consideration. Once a short line has been paired with the farthest line available to it, no inward pairing can exceed that area, because every remaining pairing is narrower. The greedy rule, always move the shorter line inward, still visits every pair that could be the maximum.

  • The tallest lines are not always the answer: Do not assume the two tallest lines give the maximum area. A moderate height with a very large width can exceed two tall lines standing close together. In the heights [1,5,6,3,4,2], the tallest lines are 6 and 5, but they are adjacent, so their width is 1 and their area is 5. The maximum here comes from heights 5 and 4 at a width of 3, which yields an area of 12.

Use these insights to reason about why the two-pointer sweep covers every scenario without missing the optimal answer.

Multiple approaches with code

We will work through two approaches and give both Python and Java implementations for each. Each approach opens with a detailed explanation of the strategy, followed by well-commented code with a main method that demonstrates usage.

Brute force approach, checking all possible pairs

Detailed explanation:

The brute force solution considers every possible pair of lines and calculates the area for each pair. Two nested loops do the work. The outer loop picks the first line i, and the inner loop picks a second line j, with j > i. For each pair (i, j), compute h = min(height[i], height[j]) and w = j - i, then take the area as h * w. Track the maximum area seen and return it at the end.

This approach is easy to write and correct by construction, since it checks every combination. Its time complexity is O(n²), which is too slow for large n. At n = 100,000 the number of pairs is around 5e9, far beyond what any submission limit allows. It only works for small input sizes.

We include it anyway, because the contrast with the optimized method is what makes the two-pointer rule look necessary rather than arbitrary.

Python implementation (brute force):

Python3
Python3

. . . .

Java implementation (brute force):

Java
Java

. . . .

Two-pointer approach, the optimal linear sweep

Detailed explanation:

The two-pointer approach is a greedy optimal solution that runs in linear time. Place one pointer at the left end, l = 0, and one at the right end, r = n-1. Those two indices are the sides of the container currently under consideration. The starting pair gives the widest container available. Compute its area, move one pointer inward, and repeat. The width narrows step by step while you search for a taller limiting line.

The critical question is which pointer to move. Always move the pointer on the side with the smaller height. The current area is capped by the shorter line. Moving the taller line inward would reduce the width while the cap stays where it is, so the area can only stay the same or shrink. Moving the shorter line inward may expose a taller line, which raises the cap and can more than repay the lost width.

The deeper reason concerns what you give up. A short line paired with the farthest line on the other side has already been measured at its maximum width. Every other pairing for that short line is narrower, and its height cap cannot rise, so no other pairing can exceed the area you just computed. Discarding that line costs you nothing.

The sweep continues until the two pointers meet. Each step updates the maximum area found so far. The algorithm is provably correct: it examines every candidate pair that could be maximal, and the pairs it skips are exactly the ones that cannot produce a larger area. That is where the speed comes from.

Step-by-step (two-pointer) algorithm:

  1. Initialize l = 0 (left pointer at start), r = n-1 (right pointer at end), and maxArea = 0.
  2. While l < r:
    • Compute h = min(height[l], height[r]) and w = r - l, then area = h * w.
    • Update maxArea = max(maxArea, area).
    • If height[l] < height[r], increment l and move the left pointer rightward, because l is the limiting side. Otherwise, when height[r] <= height[l], decrement r and move the right pointer leftward.
  3. Stop when the pointers meet. maxArea holds the result.

This approach runs in O(n) time, since the two pointers together scan the array once. Space complexity is O(1), because it keeps only a few extra variables.

If you want the pattern rather than this single question, the recurring LeetCode patterns article groups two pointers with the other techniques that cover most array questions in interviews.

Python implementation (two-pointer):

Python3
Python3

. . . .

Java implementation (two-pointer):

Java
Java

. . . .

Complexity analysis

  • Brute force approach: The brute force solution checks every pair of indices (i, j), which is on the order of n(n-1)/2 comparisons. That gives a time complexity of O(n²). For large n, say 100k, it is not feasible. The space complexity is O(1), constant extra space, since it tracks only a few indices and the running area.

  • Two-pointer approach: The two pointers traverse the array, each moving at most n steps in total. The time complexity is therefore O(n). Each step performs a constant amount of work, a few calculations and comparisons. The space complexity is O(1) as well, using a fixed number of extra variables such as the pointers and the area tracker.

In summary, the two-pointer approach turns a quadratic solution into a linear one. Given the problem constraints, that difference decides whether your submission finishes at all.

Common mistakes

  • Using the larger height instead of the smaller: A frequent mistake is to compute the area from the taller of the two lines. The water is limited by the shorter line. Using the taller line overestimates the area. Always put min(height[i], height[j]) in the formula.

  • Off-by-one errors in the width: With indices counted from zero, the width is j - i. Between index 1 and index 8 there are 7 intervals, which is exactly 8 - 1. Writing j - i - 1 or a similar adjustment miscalculates the width.

  • Moving the wrong pointer: In the two-pointer method, moving the pointer at the greater height can skip the optimal pair. Always move the pointer at the shorter line inward. When the two heights are equal, moving either pointer by one is fine.

  • Breaking out of the loop early: Some candidates add a shortcut that exits once the heights fall into a certain order. Be careful here. The sweep has to continue until the pointers meet, so that every candidate pair is evaluated. Any early exit other than l >= r can miss the true maximum.

  • Mis-initializing the maximum: Initialize maxArea to 0, since an area cannot be negative. Leaving it uninitialized, or starting from a large negative number, invites trouble for no benefit. All heights are non-negative and n >= 2, so 0 is a safe starting value, and it also covers the case where no water can be trapped.

  • Skipping pairs in brute force: If you write the brute force version, loop j from i+1, not from i. Pairing a line with itself does not form a container, and its area is 0. Check that the inner loop bound really covers every remaining line, since a mistyped limit silently drops valid pairs.

Edge cases

  • Minimum input (n=2): With two lines the answer is simply the area between them, min(height[0], height[1]) * 1. Both approaches handle it naturally. The two-pointer loop runs once, and brute force evaluates one pair. For [3, 7] the area is 3 * 1 = 3.

  • All zero heights: If every line has height 0, no water can be contained and the output is 0. Brute force computes 0 for every pair. The two-pointer sweep likewise finds only zero areas and returns 0.

  • One line significantly taller: When one line is extremely tall compared to the others, the maximum area may pair it with a line far away to gain width. Take [1, 1000, 2, 1000, 1]. The two lines of height 1000 sit at indices 1 and 3, giving min(1000,1000) * 2 = 2000, which exceeds any pairing of a tall line with a short one. The two-pointer method finds such cases correctly. The scenario to remember is this: the two tallest lines are often a good candidate, but not when they stand close together. A slightly shorter line much farther away sometimes wins, and the two-pointer sweep weighs both.

  • Multiple lines of the same height: If the array holds many equal heights, for instance all 5s or a long plateau, the best container is the two farthest lines. The two-pointer approach finds it at once. Starting at the ends gives height 5 and width n-1, for an area of 5 * (n-1), and the width only shrinks as the pointers move inward. The first area computed is the maximum in such a case. Make sure your loop does not stop early on equal heights. When the two heights match, the approach moves one pointer arbitrarily, which is safe because both sides cap the area at the same value.

  • Very large n: This is a performance note rather than a case in terms of input values. At n = 100,000 brute force will never finish in reasonable time, while the two-pointer sweep handles it in linear time. The edge consideration is to pick the linear approach for large inputs.

  • No slanting constraint: The problem says explicitly that you cannot slant the container, yet some readers still picture a diagonal placement. The sides are vertical lines at the chosen indices. Only vertical heights matter, and no angle enters the calculation.

Alternative variations

  • Uneven spacing between lines: In the classic question the lines sit at positions 0, 1, 2, ..., n-1, spaced one apart. A variation gives arbitrary x-coordinates, as pairs (x, height), and asks for the two lines that form the biggest container. The width is then the difference of the x-coordinates. The strategy barely changes. Sort the lines by x-coordinate and run the same inward sweep, computing the width as x[j] - x[i].

  • Circular arrangement: Suppose the vertical lines sit around a circle at equally spaced angles, and the width between two lines is the smaller arc distance along the circle. This version is harder, because a container can wrap from the end of the array back to the beginning. One approach duplicates the array to simulate two cycles, then applies a two-pointer technique inside a sliding window of length n. It is an advanced variation and rarely asked, though it is conceptually related.

  • K-container problem: A theoretical extension picks k lines that close a shape with the x-axis and asks you to maximize the contained area. With three lines you would form something like a triangle, though the standard constraints define only two vertical sides over the x-axis base. This is not a standard problem. Allowing more than two lines usually turns into a different problem, so treat it as a thought experiment rather than a known interview question.

  • Trapping Rain Water: The well known Trapping Rain Water problem is related, though it is not a variation on selecting two lines. There, every dip between lines traps water above the shorter neighbor, and you sum all the water across the whole array. The solution uses dynamic programming or a different form of two pointers, but it builds on the same idea of water between lines.

Practicing similar problems reinforces both the two pointers technique and the area calculation logic.

  • Trapping Rain Water (LeetCode 42): Calculate how much water an elevation map traps. It involves many containers at once and needs a different strategy, though it also turns on the minimum of the left and right bounds.

  • Largest Rectangle in Histogram (LeetCode 84): Find the largest rectangle that fits inside a histogram. Contiguous bars form the rectangle and a stack solves it, but it is another maximization under height constraints.

  • 3Sum (LeetCode 15): Find three numbers that sum to zero. It applies two pointers after sorting the array, which is the same technique in a different setting.

  • Two Sum II, input array is sorted (LeetCode 167): Find two numbers in a sorted array that add up to a target. The pointers move based on how the current sum compares with the target. It solidifies the idea of moving pointers inward.

  • Move Zeroes: The answer page for Move Zeroes uses two indices that travel in the same direction rather than toward each other, which is the other half of this pattern.

  • Is Subsequence: The Is Subsequence write-up walks one pointer per string, a useful contrast with the single-array sweep here.

Each of these problems builds the judgment to see when a brute force search over pairs can collapse into a single linear scan.

TAGS
Coding Interview Questions
Coding Interview
CONTRIBUTOR
Arslan Ahmad
Arslan Ahmad
ex-FAANG engineering manager and author or Grokking series.

GET YOUR FREE

Coding Questions Catalog

Design Gurus Newsletter - Latest from our Blog
Boost your coding skills with our essential coding questions catalog.
Take a step towards a better tech career now!
Explore Answers
What skills are needed for a backend developer?
How do I squash my last N commits together?
Two ways to combine your last two, three, or N commits into a single commit in Git, and how to push the result safely.
How to recursively list directories in C on Linux?
What is the criteria for CGPA in Cisco?
What programming language is used in interviews?
What is the highest paying job in OpenAI?
Related Courses
New
Grokking the AI System Design Interview course cover
Grokking the AI System Design Interview
Learn to design AI systems the way interviewers expect: classic ML products, LLM and RAG architectures, and agentic systems, all through the lens of the system design interview.
4.6
(3,192 learners)
Discounted price for Your Region

$99

Grokking the Coding Interview: Patterns for Coding Questions course cover
Grokking the Coding Interview: Patterns for Coding Questions
The 24 essential patterns behind every coding interview question. Available in Java, Python, JavaScript, C++, C#, and Go. The most comprehensive coding interview course with 543 lessons. A smarter alternative to grinding LeetCode.
4.6
Discounted price for Your Region

$197

Grokking Modern AI Fundamentals course cover
Grokking Modern AI Fundamentals
Master the fundamentals of AI today to lead the tech revolution of tomorrow.
4.1
Discounted price for Your Region

$72

Design Gurus logo
One-Stop Portal For Tech Interviews.
About Us
Our Team
Careers
Partners
Contact Us
Become Affiliate
Copyright © 2026 Design Gurus, LLC. All rights reserved.