The Container With Most Water problem is one of the most popular two-pointer interview questions. At first glance, a brute-force solution seems straightforward, but with a small observation, we can reduce the time complexity from O(n²)to O(n).

Let’s break it down step by step.


Problem Statement

You’re given an array heights where heights[i] represents the height of the i-th vertical bar.

Choose any two bars to form a container. The amount of water the container can hold is determined by:

  • The shorter of the two bars.
  • The distance between them.

Return the maximum amount of water that can be stored.

Example

Input:  heights = [1,7,2,5,4,7,3,6]
Output: 36

Visualization:

Height

7 |      |           |
6 |      |           |       |
5 |      |     |     |       |
4 |      |     |  |  |       |
3 |      |     |  |  |  |    |
2 |      |  |  |  |  |  |    |
1 |  |   |  |  |  |  |  |  | |
   --------------------------------
    1  7  2  5  4  7  3  6

The container formed by the bars with heights 7 and 6 (distance = 6) stores:

Area = min(7, 6) × 6 = 36

Understanding the Formula

For any two bars:

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

The minimum height determines the water level because water spills over the shorter side.

The width is simply the distance between the two indices.


Solution 1: Brute Force

The most straightforward approach is to try every possible pair.

from typing import List

class Solution:
    def maxArea(self, heights: List[int]) -> int:
        mArea = 0
        for i in range(len(heights)):
            for j in range(i + 1, len(heights)):
                area = min(heights[i], heights[j]) * (j - i)
                mArea = max(area, mArea)
        return mArea

How it works

For every index:

  • Pick a second index after it.
  • Compute the area.
  • Keep track of the maximum.

Example

[1,7,2,5]

Pairs checked:

(1,7)
(1,2)
(1,5)
(7,2)
(7,5)
(2,5)

Every possible container is evaluated.

Complexity

  • Time: O(n²)
  • Space: O(1)

While this works, it becomes inefficient for larger arrays because we’re checking far too many combinations.


Can We Do Better?

Notice something interesting.

Suppose our pointers are here:

1   8   6   2   5   4   8   3   7
L                               R

Current area:

min(1,7) × width

The limiting factor is the height 1.

Even if we move the taller bar inward, the minimum height is still 1, while the width decreases.

That means the area can never increase.

So moving the taller bar is pointless.

Instead, we move the shorter bar, hoping to find a taller one.

This single observation leads to the optimal solution.


Solution 2: Two Pointers

We start with the widest possible container.

One pointer begins at the left.

The other begins at the right.

After computing the area:

  • Move the shorter pointer inward.
  • Repeat until the pointers meet.
from typing import List

class Solution:
    def maxArea(self, heights: List[int]) -> int:
        l, r = 0, len(heights) - 1
        maxArea = 0

        while l < r:
            currentArea = min(heights[l], heights[r]) * (r - l)
            maxArea = max(maxArea, currentArea)

            if heights[l] < heights[r]:
                l += 1
            else:
                r -= 1

        return maxArea

Why Move the Shorter Pointer?

Let’s say we have:

3                 8
L                 R

Current area:

min(3,8) × width

If we move the right pointer:

3             5
L             R

The width becomes smaller.

The minimum height is still at most 3.

The area cannot improve.

But if we move the left pointer:

7             8
L             R

Now we’ve found a taller left bar.

Although the width shrank slightly, the minimum height increased significantly, giving us a chance to find a larger area.

This is the key insight behind the two-pointer technique.


Dry Run

Input:

[1,7,2,5,4,7,3,6]

Left

Right

Heights

Width

Area

Max

0

7

(1,6)

7

7

7

1

7

(7,6)

6

36

36

1

6

(7,3)

5

15

36

1

5

(7,7)

4

28

36

1

4

(7,4)

3

12

36

1

3

(7,5)

2

10

36

1

2

(7,2)

1

2

36

Final answer:

36

Complexity Analysis

Brute Force

  • Time: O(n²)
  • Space: O(1)

Two Pointers

  • Time: O(n)
  • Space: O(1)

The two-pointer solution is significantly faster because each pointer moves at most n times.


Key Takeaways

  • The container’s height is determined by the shorter bar.
  • Starting with the widest container gives us the maximum possible width.
  • Moving the taller pointer can never produce a better answer because the width decreases while the limiting height stays the same.
  • Always move the shorter pointer in hopes of finding a taller bar that can compensate for the reduced width.
  • This transforms a quadratic solution into a linear one, making it the optimal approach.

The biggest lesson from this problem isn’t just learning the two-pointer technique—it’s recognizing how a simple observation can eliminate unnecessary work. Instead of checking every possible pair, we use the properties of the problem to make a greedy decision at each step, resulting in an elegant O(n) solution.