DSA sheet · Binary Search · Classic binary search pattern

Sqrt(x)

Second question of the classic pattern (LeetCode 69). Find the square root of x, rounded down, without using any built-in square root or power function. There is no array here. The teacher draws the numbers 1, 2, 3, … on a number line and notices that this line is already sorted, so we can binary search over numbers instead of indexes.

Along the way she explains four ideas that come back again and again: (1) how to pick a tighter search range (up to x/2, and why not x/3), (2) base cases, (3) why mid is written as left + (right - left) / 2, derived on the number line, and (4) overflow when squaring big numbers in Java, which she hits live in the video and fixes.

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

Part 0 · Before starting

Square, square root, rounded down

Why binary search works here

Binary search needs a sorted order or a yes/no question that flips only once. Ask "is m × m ≤ x?" for m = 1, 2, 3, …:

m123456
m²149162536
≤ 8 ?yesyesnonononoanswer = the last "yes" = 2

Squares grow as m grows, so the answers are yes yes … yes no no …. It flips only once (it's monotonic). So at any mid, comparing mid² with x tells us which side to keep.

low/high and the mid formula

left and right mark the ends of the range of numbers we're still considering. mid is the number halfway between them. We compute it as mid = left + (right - left) // 2. The teacher derives why in Part B, step 4. In short: in Java/C++, left + right can go past the int limit 2³¹ − 1 and become negative (overflow). Python ints never overflow, so in Python it's only a good habit.


Part A · Brute force: try 1, 2, 3, … one by one

LeetCode 69

1The question in simple words

You get a non-negative integer x (0 or more). Return the square root of x rounded down to a whole number. The answer is non-negative too. You may not use built-ins like math.sqrt, x ** 0.5 or pow.

2What the constraints tell us

3Intuition

The answer is the last m whose square is still ≤ x. So start at 1 and keep going up while the square is still ≤ x. The teacher mentions that this problem can be solved without binary search, and that the point of the video is to learn to think of binary search in such cases.

4Building the logic

5Approach steps

  1. If x < 2, return x.
  2. m = 1. While (m + 1) × (m + 1) ≤ x: m += 1.
  3. Return m.

6Code (Python)

Sqrt(x), brute force
class SolutionBrute:
    def mySqrt(self, x):
        if x < 2:
            return x                    # sqrt(0) = 0, sqrt(1) = 1
        m = 1
        while (m + 1) * (m + 1) <= x:   # next number still fits
            m += 1
        return m

7Code line by line

linewhat it means
if x < 2: return x0 and 1 are their own square roots.
while (m + 1) * (m + 1) <= x:If the next number's square still fits under x, the answer is at least m + 1.
m += 1Move up one number.
return mThe next square would pass x, so m is the biggest one that fits.

8Dry run

x = 8: m = 1 → is 2² = 4 ≤ 8? yes → m = 2 → is 3² = 9 ≤ 8? no → return 2 ✓.

9Complexity & remember

Remember brute forceWalk up from 1 until the next square passes x. Fine, but it ignores that the number line is sorted.

Part B · Binary search on the number line

1The question

Same question. Find the biggest m with m × m ≤ x, in O(log x) steps.

2What the constraints tell us

Same as Part A. Here they matter even more. right can be up to about 2³⁰, so in Java/C++ both left + right and mid * mid can overflow an int. In Python neither can.

3Intuition: the number line is a sorted array

Draw the numbers 1 2 3 4 5 6 7 8 on a line. For x = 8, can 7 be the square root? No, 7² = 49. Can 6? No. 5? No. The answer sits somewhere around 2 or 3. For x = 25, the line goes up to 25, and 10 or 14 are obviously too big. The answer, 5, is far to the left. For x = 4, the answer is 2.

These numbers are in sorted order, just like a sorted array. So instead of walking them one by one, jump to the middle number, square it, and compare with x. Too small → go right. Too big → go left.

4Building the conditions from examples

Idea 1: the right end can be x/2, not x

In all those examples the square root was well to the left of x. The teacher's claim: the square root is always ≤ x/2, so we can start right at x // 2 instead of x. That's one fewer halving, and it keeps the numbers smaller.

Doubt: why x/2? Why not an even tighter x/3?
→ She picked the example x = 4 on purpose. √4 = 2. With x/2 the range ends at 4 // 2 = 2, which still includes the answer ✓. With x/3 it ends at 4 // 3 = 1, which cuts off the answer 2 ✗. For 8 and 25, x/3 would happen to be fine, but not for 4. So x/2 is the safe limit for every x, small or large.
(Why it's always true for x ≥ 4: √x ≤ x/2 is the same as 2 ≤ √x, i.e. x ≥ 4. For x = 2 and 3, the answer is 1, and x // 2 = 1 is still fine.)

Idea 2: base cases for 0 and 1

If x is 0, the root is 0. If x is 1, the root is 1. So when x < 2, return x itself.

Doubt: is this base case really needed, or just a shortcut?
→ It's needed. We start left at 1 and right at x // 2. For x = 1, right = 0, so left > right from the start. The loop never runs, and we would return right = 0, which is wrong. For x = 0, right = 0 as well. The base case covers both cleanly.

Idea 3: why mid = left + (right - left) / 2 (her number-line derivation)

Earlier (in Binary Search Basics) we used (left + right) / 2, because the values were small. Here right can be close to 2³¹ − 1, the biggest int in Java. Adding left to it can push past the maximum and give a negative number. So we need a formula that gives the same middle without adding two big numbers.

Her derivation: put left at 3 and right at 8 on the line.

  1   2   3   4   5   6   7   8
          L           m       R
          |<-- (R - L) = 5 -->|
          |<- 5/2 ->|

R - L is never bigger than R, so this never creates a number bigger than the inputs. That's why we use it whenever x is so big that one more addition could make it negative.

Idea 4: compare mid² with x (three cases)

Let square = mid * mid. The target we compare with is x.

While explaining, she says "square less than mid" once; she means "square less than x". The code compares with x.

Idea 5: loop while left <= right, then return right

If x isn't a perfect square (like 8), the equal case never happens. left keeps moving right and right keeps moving left, until they cross. Then the loop stops. Which one is the answer?

Doubt: why is right always exactly the rounded-down root at the end?
→ Look at how the pointers move. left only ever moves past a number whose square was < x, so everything below left has square ≤ x. right only ever moves below a number whose square was > x, so everything above right has square > x. When they cross, right = left − 1. So right is the last number with square ≤ x, and right + 1 = left has square > x. That's exactly the floor of √x.
Doubt: while typing the code she says while left < right. Is that OK?
→ No, it must be <=, which is what she says on the whiteboard ("left less than or equal to right"). With < the loop stops when one number is still unchecked, and returning right can be wrong. Example x = 6: left = 1, right = 3 → mid 2, 4 < 6 → left = 3. Now left == right, so < stops and returns right = 3. But 3² = 9 > 6, the correct answer is 2. With <=: mid 3, 9 > 6 → right = 2 → crossed → return 2 ✓. The notes use <=.

Idea 6: overflow in mid × mid (the bug she hits live)

In Java she first stored mid * mid into a long variable, but still got a wrong answer on a big test. The reason: mid was an int, so int × int is computed as an int first. It overflows before being stored in the long. Saving it into a long afterwards is too late.

Her fix: make mid a long (and so left and right too, since they get assigned from mid), and cast back to int when returning. She also had a small typo along the way: she forgot to write mid in the return cast, got a compile error, and fixed it.

Doubt: do I need any of this in Python?
→ No. Python ints have no size limit, so mid * mid is always exact, and there are no casts. But know the story for interviews in Java/C++: the type of the operands decides the type of the multiplication, not the variable you store it in.

5Approach steps

  1. If x < 2 → return x.
  2. left = 1, right = x // 2.
  3. While left <= right: mid = left + (right - left) // 2, square = mid * mid.
  4. square == x → return mid. square < x → left = mid + 1. Else → right = mid - 1.
  5. After the loop → return right (the rounded-down root).

6Code (Python)

Sqrt(x), binary search
class Solution:
    def mySqrt(self, x):
        if x < 2:
            return x                     # 0 -> 0, 1 -> 1
        left, right = 1, x // 2          # the root is never above x/2
        while left <= right:
            mid = left + (right - left) // 2
            square = mid * mid           # Python ints don't overflow
            if square == x:
                return mid               # perfect square
            elif square < x:
                left = mid + 1           # need a bigger number
            else:
                right = mid - 1          # need a smaller number
        return right                     # crossed: right = floor(sqrt(x))

7Code line by line

linewhat it means
if x < 2: return xBase cases 0 and 1. Also needed because for x = 1 the range [1, 0] would be empty.
left, right = 1, x // 2The search range is numbers, not indexes: from 1 to x/2.
while left <= right:Keep going while at least one number is unchecked.
mid = left + (right - left) // 2The middle number, written the overflow-safe way.
square = mid * midCompare the square instead of taking a root. (Java/C++ need long here.)
if square == x: return midExact root found.
elif square < x: left = mid + 1mid is too small, and so is everything below it.
else: right = mid - 1mid is too big, and so is everything above it.
return rightleft passed right. right is the biggest number whose square fits.

8Dry run

Her example: x = 8

stepleftrightmidmid²decisionwhat we throw away
1141 + 3 // 2 = 244 < 8 → left = 31, 2
234399 > 8 → right = 23, 4
end32crossed: left ahead, right behind → return right = 2 (√8 ≈ 2.83, rounded down)
number1234
step 112342² = 4 < 8
step 212343² = 9 > 8
end1234right = 2 ← answer

A perfect square: x = 16

stepleftrightmidmid²decision
118416equal → return 4

The biggest input: x = 2,147,483,647

right starts at 1,073,741,823. About 30 halvings later, the loop ends with right = 46340 (46340² = 2,147,395,600 ≤ x, while 46341² = 2,147,488,281 > x). In Java, 46341² is bigger than the int max, which is exactly where the int × int overflow bites.

9Complexity & remember

Remember Sqrt(x)Binary search over numbers 1 … x//2. Compare mid*mid with x. Equal → mid. Smaller → go right. Bigger → go left. Loop <=, then return right (rounded down). x < 2 → return x.

Part C · Revision page

brute forcebinary search
ideawalk up 1, 2, 3, … while the next square fitsjump to the middle number, compare its square with x
range1 upward1 … x // 2
answer when not exactlast m that fitsright after the pointers cross
timeO(√x)O(log x)
spaceO(1)O(1)
trapJava / C++Python
left + right near 2³¹overflows → use left + (right - left) / 2safe, still use the safe form
mid * midoverflows as int × int even if stored in a long → make mid a longsafe
return typecast the long back to intnothing to do
If you remember only 5 lines 1. The number line 1, 2, 3, … is sorted, so binary search on numbers.
2. The root is ≤ x/2 (x/3 fails for x = 4). Base case x < 2 → x.
3. mid = left + (right − left) // 2: start at left, walk half the gap.
4. mid² vs x: equal → mid · less → left = mid + 1 · more → right = mid − 1.
5. Loop with <=. After crossing, right is the rounded-down root.
Mistakes to avoid ✗ using math.sqrt / ** 0.5 (not allowed)
✗ forgetting the x < 2 base case (x = 1 would return 0)
✗ while left < right with this code (x = 6 gives 3)
✗ returning left (that's the rounded-up side)
✗ in Java: long sq = mid * mid with int mid (overflows first)
test it yourself (paste under the solutions above)
s = Solution()
for x in [0, 1, 4, 6, 8, 16, 25, 2147483647]:
    print(x, s.mySqrt(x), SolutionBrute().mySqrt(x))
# 0 0 0 / 1 1 1 / 4 2 2 / 6 2 2 / 8 2 2 / 16 4 4 / 25 5 5 / 2147483647 46340 46340

Based on this video: Sqrt(x) | Classic Binary Search pattern