DSA sheet · Arrays · Two Pointers pattern

Container With Most Water

The teacher calls this one of the most asked and most famous interview questions. We get the heights of vertical lines and must pick two lines that hold the most water between them. She first writes the brute force (try every pair, O(n²)), shows from the constraints that it will time out, and then uses opposite-end two pointers. The whole trick is one decision: after measuring a container, which wall do we move inward?

Why it matters: it shows that two pointers don't need a sorted array; they need a rule that tells you which pointer can safely move. That rule ("move the shorter wall") comes back in Trapping Rain Water right after this.

Every part below follows the same order:
① the question in simple words → ② what the constraints tell us → ③ intuition → ④ building the logic from examples → ⑤ approach steps → ⑥ code → ⑦ code line by line → ⑧ dry run → ⑨ complexity & remember

Part 0 · Before starting

What is a pointer here?

A pointer is just a variable holding an index into the array. "left points to 2" means left = 2, and height[left] is the value there.

The brute force over all pairs, and when we can skip pairs

When an answer depends on two positions (i, j), the simplest plan is two nested loops over every pair: n·(n−1)/2 pairs, which is O(n²). Two pointers is a way to skip whole groups of pairs because we can prove they can't beat what we already have. Usually that proof comes from the array being sorted. In this problem it comes from the area formula itself (Part B).

Opposite-end pointers

left starts at index 0 and right at index n − 1. Each step we look at the pair (left, right), then move exactly one of them inward. They meet after about n steps, so the work is O(n). The only hard part is the rule for which one to move. The rule must guarantee that the pointer we move away from can't be part of a better answer, so no good pair is ever skipped.

kindhow the pointers moveexample
Opposite ends (this page)left from the start, right from the end, towards each otherpair sum in a sorted array, this problem, palindromes
Same directionslow/fast or write/read pointers, both moving rightMove Zeroes
Three pointerslow, mid, highSort Colors
Fix one + two pointersloop over one element, opposite ends on the rest3Sum

Why two pointers and not sliding window, prefix sum or Kadane's?

The teacher's four array patterns are two pointers, sliding window, prefix sum and Kadane's. The last three are for a contiguous subarray where every value in between matters (a sum, an average, a length under some rule). Here the area uses only the two chosen lines: the shorter height and the distance between them. The lines in between don't change the area at all. "Two points, nothing in between" → two pointers.

Doubt: but the width is "everything between the two lines". Isn't that a window?
→ The width is just right − left, a number we get from the two indexes. We never read the heights in between. A window pattern is needed only when the values inside matter.

Part A · Brute force: every pair of lines

LeetCode 11

1The question in simple words

You get a list height of n numbers. Imagine n vertical lines (planks) standing on the x-axis: line i stands at x = i and is height[i] tall. Choose two lines. Together with the floor they form a container. Return the largest amount of water any such container can hold. You can't tilt the container.

  height = [1, 8, 6, 2, 5, 4, 8, 3, 7]

   8 |    █              █
   7 |    █~~~~~~~~~~~~~~█~~~~~█
   6 |    █~~█~~~~~~~~~~~█~~~~~█
   5 |    █~~█~~~~~█~~~~~█~~~~~█
   4 |    █~~█~~~~~█~~█~~█~~~~~█
   3 |    █~~█~~~~~█~~█~~█~~█~~█
   2 |    █~~█~~█~~█~~█~~█~~█~~█
   1 | █  █~~█~~█~~█~~█~~█~~█~~█
     +---------------------------
       0  1  2  3  4  5  6  7  8      ~ = water between line 1 and line 8

The best choice is line 1 (height 8) and line 8 (height 7). Water can only rise to 7 (the shorter one), and they are 8 − 1 = 7 apart. Area = 7 × 7 = 49.

Doubt: the lines in between (6, 2, 5…) stick into the water. Don't they reduce it?
→ Not in this problem. The lines are treated as thin, with no width, so they take no space. Only the two chosen lines form the container. (In Trapping Rain Water, the next problem, the bars do take space. That's the big difference.)

2What the constraints tell us

3Intuition: the shorter line decides the level

Pour water between two lines. It rises until it reaches the top of the shorter line; any more spills over that side. So:

Area of a containerarea = min(height[left], height[right]) × (right − left)
height = the shorter line · width = the distance between the indexes

The brute force just tries this for every pair and keeps the biggest.

4Building the logic from the example

First pairs: line 0 with everyone after it

Then line 1 with every line after it, then line 2 with every line after it, and so on. That's two loops: left goes over every index, and right goes from left + 1 to the end. Starting right at left + 1 means we never pair a line with itself and never count the same pair twice.

Keeping the best

Each area is compared with a max_area variable that lives outside both loops: max_area = max(max_area, area). After the loops, it holds the answer.

5Approach steps

  1. max_area = 0.
  2. For every left from 0 to n − 1, for every right from left + 1 to n − 1:
  3. height of water = min of the two lines, width = right − left, area = height × width.
  4. Update max_area.
  5. Return max_area.

6Code (Python)

Brute force: O(n²), TLE for n = 10⁵
class Solution:
    def maxArea(self, height):
        n = len(height)
        max_area = 0
        for left in range(n):
            for right in range(left + 1, n):
                h = min(height[left], height[right])   # water stops at the shorter line
                w = right - left                       # distance between the lines
                max_area = max(max_area, h * w)
        return max_area

7Code line by line

linewhat it means
max_area = 0The best seen so far. 0 is safe: no area is negative.
for left in range(n):Pick the first line of the pair.
for right in range(left + 1, n):Pick the second line, always to the right of the first, so each pair is tried once.
h = min(height[left], height[right])The water level: the shorter of the two.
w = right - leftHow far apart they are.
max_area = max(max_area, h * w)Keep the bigger of the old best and this container.

8Dry run (hand table, first rows)

height = [1, 8, 6, 2, 5, 4, 8, 3, 7].

leftrightheightsmin × widthareamax_area
011, 81 × 111
021, 61 × 222
0…81, …1 × up to 8up to 88
128, 66 × 168
168, 88 × 54040
188, 77 × 74949
2 ………nothing larger…49

36 pairs in total for n = 9. The answer is 49.

9Complexity & remember

Remember the brute forceEvery pair (left < right): min(h[l], h[r]) × (r − l), keep the max. Correct, but O(n²).

Part B · Optimal: two pointers, move the shorter wall

LeetCode 11

1The question again, with the new goal

Same question, now in O(n): one sweep where left starts at the first line, right at the last, and one of them moves inward every step.

2"But two pointers needs a sorted array…"

The teacher raises this herself. In earlier problems the array was sorted, and that's what told us which pointer to move. Here the heights are not sorted. Her answer: sorting was never the goal. It was only a way to decide which pointer to move. If we can make that decision some other way, we can still use two pointers. Here the area formula gives us the rule.

3Intuition: start as wide as possible, then trade width for height

Start with the widest container: line 0 and line n − 1. From now on, any move makes the container narrower, by one. Area = height × width, and the width only shrinks. So the only hope of a bigger area is to find a taller water level. That gives a greedy idea: keep the taller wall and throw away the shorter one, hoping the next line is taller.

4Building the rule: why moving the shorter wall is the only move that can help

Her small example: walls of height 2 and 5, three apart

Left wall 2 (at index 2), right wall 5 (at index 5). Width 3, level min(2, 5) = 2, area 2 × 3 = 6. Now we must move one wall inward. Which one?

The full reason: the shorter wall has already done its best

Say height[left] ≤ height[right]. Think about every container that still uses line left, paired with any line between left and right:

Narrower and not higher → every one of those containers is ≤ the area we just measured. So line left has nothing better to offer: its best container is the one we already recorded. Dropping it (left += 1) can't skip the answer. That's why the shorter wall is the one to move.

The rule in one sentenceThe shorter wall caps the level. Pairing it with anything closer only loses width, so its best is already counted. Move the shorter one; keep the taller one for later.
Doubt 1: what goes wrong if I move the taller wall instead?
→ You can skip the answer. In the main example, the first pair is line 0 (height 1) and line 8 (height 7). If you moved the 7 inward, line 8 would be gone forever, and the best container, line 1 with line 8 (area 49), would never be checked.
Doubt 2: what if both walls are equal?
→ Move either one. With equal walls, the argument above works for both: any container using either of them with a closer partner is narrower and no higher. The teacher writes if height[left] <= height[right]: left += 1, so on a tie she moves left.
Doubt 3: while left < right or left <= right?
→ <. A container needs two different lines. When left = right there's only one line, width 0, nothing to measure. The teacher thinks about adding = and decides against it for exactly this reason.

5Approach steps

  1. left = 0, right = n − 1, max_area = 0.
  2. While left < right:
  3. area = min(height[left], height[right]) × (right − left); update max_area.
  4. If height[left] ≤ height[right] → left += 1; else → right -= 1.
  5. Return max_area.

6Code (Python)

Optimal: two pointers, O(n) time, O(1) space
class Solution:
    def maxArea(self, height):
        left, right = 0, len(height) - 1
        max_area = 0
        while left < right:                            # need two different lines
            h = min(height[left], height[right])       # level = shorter wall
            w = right - left                           # width
            max_area = max(max_area, h * w)
            if height[left] <= height[right]:          # left is the shorter wall
                left += 1                              # its best is already counted
            else:
                right -= 1                             # right is shorter: drop it
        return max_area

On screen the teacher first typed the width the wrong way round, got a wrong answer, and fixed it to right − left ("my bad"). If your answers come out negative or tiny, check the width first.

7Code line by line

linewhat it means
left, right = 0, len(height) - 1The widest container: first line and last line.
max_area = 0Best so far. (She says 0 or −1 both work as a start, since any real area is ≥ 0.)
while left < right:Stop when the pointers meet: one line can't hold water.
h = min(height[left], height[right])Water rises only to the shorter wall.
w = right - leftDistance between the walls.
max_area = max(max_area, h * w)Record this container if it's the best yet.
if height[left] <= height[right]: left += 1Left is the shorter (or equal) wall. Every container it could still make is ≤ this one, so throw it away.
else: right -= 1Right is the shorter wall; same reasoning, so throw it away.

8Dry run

height = [1, 8, 6, 2, 5, 4, 8, 3, 7], n = 9. This is the same walk the teacher does on screen.

stepleftrightheights at L, Rmin × width = areadecision (which pointer moves, why)max_area
1081, 71 × 8 = 81 < 7 → left is shorter → left +18
2188, 77 × 7 = 497 < 8 → right is shorter → right −149
3178, 33 × 6 = 183 < 8 → right −149
4168, 88 × 5 = 40tie → move left (her choice)49
5266, 86 × 4 = 246 < 8 → left +149
6362, 82 × 3 = 62 < 8 → left +149
7465, 85 × 2 = 105 < 8 → left +149
8564, 84 × 1 = 44 < 8 → left +149
end66––left = right → stop49 ✓

The array at key moments. Yellow = the two walls being measured, grey = lines already thrown away.

step 1186254837area 8; the 1 is shorter → drop it
LR
step 2186254837area 49, the answer; now the 7 is shorter → drop it
LR
step 41862548378 and 8: area 40, a tie → move left
LR

Only 8 containers measured instead of 36, and none of the skipped ones could have beaten 49.

9Complexity & remember

Remember Container With Most WaterStart widest. Measure min(h[l], h[r]) × (r − l). Move the shorter wall: everything it could still make is narrower and no higher, so its best is already counted. Loop while l < r.

Part C · Revision page

Brute force (Part A)Two pointers (Part B)
pairs checkedall n(n−1)/2about n
why it's safechecks everythinga dropped shorter wall can only make smaller containers
time / spaceO(n²) / O(1)O(n) / O(1)
n = 10⁵?TLEpasses
situationmovewhy
h[left] < h[right]left += 1left caps the level; its best is counted
h[left] > h[right]right −= 1right caps the level; its best is counted
equaleither (she moves left)both have done their best
If you remember only 5 lines 1. Area = shorter wall × distance. The lines in between don't matter.
2. Brute force over all pairs is O(n²): TLE at n = 10⁵.
3. Start with the widest pair (0, n − 1). Every move makes it narrower.
4. Move the shorter wall: it can't do better with a closer partner.
5. Stop when left meets right. One pass, O(n).
Mistakes to avoid ✗ moving the taller wall (you can skip the best pair)
✗ width as left − right (negative) or right − left + 1 (that counts bars, not distance)
✗ using max of the two heights instead of min
✗ subtracting the lines in between (that's Trapping Rain Water, not this)
✗ while left <= right (measures a single line; harmless here but meaningless)
test it yourself (paste under either solution above)
s = Solution()
print(s.maxArea([1, 8, 6, 2, 5, 4, 8, 3, 7]))   # 49
print(s.maxArea([1, 1]))                        # 1
print(s.maxArea([4, 3, 2, 1, 4]))               # 16
print(s.maxArea([0, 0]))                        # 0
print(s.maxArea([1, 2, 3, 4, 5]))               # 6

Based on this video: Container With Most Water | Two Pointers