Rahul Kumar

So far in this challenge we have dealt with problems that are clearly array manipulation tasks. Move elements, find sums, collect triplets.
Day 4 is different. LeetCode 11 Container With Most Water presents itself as a geometry problem. You see lines, containers, water. It feels visual.
But underneath the visual framing is a pure logic problem about tradeoffs. Width versus height. And once you see that tradeoff clearly, the two pointer solution becomes the only approach that makes sense.
You are given an integer arrayheightof lengthn. There arenvertical lines drawn such that the two endpoints of theith line are(i, 0)and(i, height[i]). Find two lines that together with the x-axis form a container such that the container contains the most water. Return the maximum amount of water a container can store. You may not slant the container.
Examples:
Input: height = [1, 8, 6, 2, 5, 4, 8, 3, 7]
Output: 49
Input: height = [1, 1]
Output: 1Constraints:
2 <= height.length <= 10^50 <= height[i] <= 10^4Before we think about pointers, we need to be completely clear on how area is calculated.
When we pick two lines at indices left and right, the water they can hold is limited by:
right - leftSo the formula is:
area = min(height[left], height[right]) * (right - left)This formula is the entire foundation of the problem. Every decision we make in the algorithm comes back to this formula and what we can do to maximise it.
The brute force instinct is to check every possible pair of lines, calculate the area for each, and track the maximum. That is O(n²). For an array of 100,000 elements, that is 10 billion pair checks. Too slow by a wide margin.
A smarter instinct might be to look for the tallest lines and check those first. But the tallest line is not always part of the answer. What matters is the combination of height and width together.
The right instinct comes from asking a sharper question:
If I have two pointers at opposite ends, and I move one step inward, which pointer should I move to give myself the best chance of finding a larger area?
The answer to that question is the entire algorithm.
Here is the key observation that makes this problem solvable in O(n):
When we have two lines, the area is always determined by the shorter one. The taller line is irrelevant beyond its role as one wall of the container. Even if we replaced the taller line with something even taller, the area would not change because the shorter line is still the ceiling.
Now imagine we are standing at the widest possible configuration: left at index 0 and right at the last index. This is the maximum possible width. Every move we make inward shrinks the width.
If we shrink width, we need to gain height to compensate. That means we need to find a taller minimum height. The only way that can happen is if we replace the current shorter line with something taller.
So what do we do? We move the pointer pointing to the shorter line. Moving the pointer pointing to the taller line can never help because:
Moving the shorter line is our only chance at improvement. This is the insight interviewers want to hear.
left at index 0 (the leftmost line)right at the last index (the rightmost line)right by convention)left and right meetheight = [1, 8, 6, 2, 5, 4, 8, 3, 7]
index 0 1 2 3 4 5 6 7 8Initial state:
left = 0 (height = 1)
right = 8 (height = 7)
ans = 0[1] 8 6 2 5 4 8 3 [7]
^ ^
left=0 right=8
height[left] = 1
height[right] = 7
h = min(1, 7) = 1
w = 8 - 0 = 8
area = 1 * 8 = 8
ans = 8height[left] (1) < height[right] (7), so the left line is the bottleneck.
Action: Move left right.
left = 1
Why not move right? Moving right left would reduce width while keeping the same bottleneck height of 1. That can only make things worse or stay the same.
1 [8] 6 2 5 4 8 3 [7]
^ ^
left=1 right=8
height[left] = 8
height[right] = 7
h = min(8, 7) = 7
w = 8 - 1 = 7
area = 7 * 7 = 49
ans = 49height[left] (8) > height[right] (7), so the right line is the bottleneck.
Action: Move right left.
right = 7
This is our best answer. Even though we continue checking, no future pair will beat 49.
1 [8] 6 2 5 4 8 [3] 7
^ ^
left=1 right=7
height[left] = 8
height[right] = 3
h = min(8, 3) = 3
w = 7 - 1 = 6
area = 3 * 6 = 18
ans = 49 (unchanged)height[right] (3) < height[left] (8), so the right line is the bottleneck.
Action: Move right left.
right = 6
1 [8] 6 2 5 4 [8] 3 7
^ ^
left=1 right=6
height[left] = 8
height[right] = 8
h = min(8, 8) = 8
w = 6 - 1 = 5
area = 8 * 5 = 40
ans = 49 (unchanged)Heights are equal. Moving either pointer is valid. We move right by convention.
Action: Move right left.
right = 5
Iteration 5: left=1 (8), right=5 (4)
h = min(8, 4) = 4, w = 4, area = 16 (ans unchanged)
Move right (4 < 8)
Iteration 6: left=1 (8), right=4 (5)
h = min(8, 5) = 5, w = 3, area = 15 (ans unchanged)
Move right (5 < 8)
Iteration 7: left=1 (8), right=3 (2)
h = min(8, 2) = 2, w = 2, area = 4 (ans unchanged)
Move right (2 < 8)
Iteration 8: left=1 (8), right=2 (6)
h = min(8, 6) = 6, w = 1, area = 6 (ans unchanged)
Move right (6 < 8)
right = 1 → left == right → loop endsans = 49
Formed by lines at index 1 (height 8) and index 8 (height 7).
Let us think about why this approach never misses the optimal answer.
1. We start at the widest configuration and only move inward.
At any point, we have considered the best possible area for the current pair of lines. The only moves available shrink the width. So every future pair has less width than the current one.
2. Moving the shorter pointer is the only rational choice.
Suppose the optimal answer uses lines at positions i and j. At some point during our traversal, left will be at i and right will be at j (or vice versa). The algorithm will calculate that area and update ans. It cannot miss this pair because we never skip positions, we only move inward one step at a time.
3. Moving the taller pointer is never beneficial and we can prove it.
Say height[left] < height[right]. The current area is height[left] * (right - left). If we move right inward to right - 1, the new area is at most height[left] * (right - left - 1), which is strictly less than the current area regardless of what height[right - 1] is, because height[left] is still the ceiling. We provably cannot do better by moving the taller pointer.
This is the proof that makes the greedy choice correct.
var maxArea = function(height) {
let left = 0;
let right = height.length - 1;
let ans = 0;
while (left < right) {
let h = Math.min(height[left], height[right]); // Shorter line is the ceiling
let w = right - left; // Width between pointers
if (h * w > ans) ans = h * w; // Update max if better
if (height[left] < height[right]) {
left++; // Move the bottleneck pointer
} else {
right--; // Move right if equal or taller
}
}
return ans;
};Line by line breakdown:
left = 0 and right = height.length - 1 initialise pointers at the widest possible configuration.ans = 0 tracks the best area seen so far.h = Math.min(height[left], height[right]) picks the shorter line since water cannot exceed it.w = right - left is the horizontal distance between the two lines.if (h * w > ans) ans = h * w updates the answer only when we find a better area.if (height[left] < height[right]) left++ moves the left pointer when it is the shorter one.else right-- moves the right pointer when it is shorter or both are equal.left >= right since we need two distinct lines.| Complexity | Explanation | |
|---|---|---|
| Time | O(n) | Each pointer moves at most n times total. The two pointers together traverse the array exactly once. |
| Space | O(1) | Only three variables are used: left, right, and ans. No extra data structures. |
Mistake 1: Moving the wrong pointer.
// Wrong: moving the taller pointer
if (height[left] > height[right]) left++;
else right--;This is backwards. You must move the pointer with the smaller height, not the larger one. Moving the larger pointer can never improve the area.
Mistake 2: Using height[left] <= height[right] to move left.
// Subtle bug when heights are equal
if (height[left] <= height[right]) left++;
else right--;When heights are equal, moving either pointer is valid. But this specific formulation will always move left, which is fine. Just make sure you are consistent and understand why both choices are correct when heights are tied.
Mistake 3: Initialising ans with the first pair's area.
// Unnecessary and brittle
let ans = Math.min(height[0], height[height.length - 1]) * (height.length - 1);Just initialise ans = 0. The update check inside the loop will handle the first pair correctly on the first iteration.
Mistake 4: Stopping the loop too early.
// Wrong: stops before all pairs are checked
while (left < right - 1)The loop must run as long as left < right. When left === right, both pointers are on the same line, which is invalid. But left = right - 1 is a valid pair and should not be skipped.
If Container With Most Water comes up in an interview, here is how to structure your thinking:
area = min(height[left], height[right]) * (right - left)."The "move the limiting factor" two pointer approach appears in several related problems:
Container With Most Water is the cleanest entry point into this family of problems. Understand it well and the others become far more approachable.
Day 4 ✅
Four days in. Four patterns mapped. Each one building on the previous.
Day 5 is coming tomorrow with a new problem, a full dry run, and the same structured breakdown.
If you are following along, drop a comment with which iteration surprised you the most or any part of the pointer movement logic that felt unclear. I will answer.
See you on Day 5. 🚀
This post is part of my 250 Days DSA Challenge, solving one problem every day with a focus on intuition, patterns, and interview ready thinking.
LangChain's Model component is the core of every AI app. Learn how LLMs, Chat Models, and Embeddings actually work - from token generation to semantic search - with practical code and real-world examples.
Sameer Singh
Learn the 6 core building blocks of LangChain - Models, Prompts, Chains, Indexes, Memory, and Agents. Understand what each component does, why it exists, and how they work together to power real AI apps.
Sign in to join the discussion.
Sameer Singh