Skip to content

Beginner: foundations · Thinking in Big-O · topic 4 of 61 · 35 min

Best, average and worst case; analysing loops and recursion

Explanation

The same code can be fast on one input and slow on another, so we talk about best, average and worst cases; interviews usually mean the worst case. A loop that halves or doubles its counter runs O(log n) times. For recursion, draw a recursion tree: add up the work on each level and count the levels.

One algorithm, three cases

Linear search looks at items one by one until it finds the target. If the target is the first item, it takes one step: that is the best case, O(1). If the target is last or missing, it checks all n items: the worst case, O(n). On average, for a target that is present at a random position, it checks about n/2 items, which is still O(n).

The worst case is the safe promise: the code will never be slower than this. So when someone says "linear search is O(n)" they mean the worst case. When an interviewer asks for the complexity, give the worst case unless they ask for something else, and mention the average case if it is very different.

Quicksort, which you will meet in Module 6, is the famous example where the cases differ. On average it takes O(n log n). But if the pivot it picks is always the smallest or largest item (for example, always picking the first item of an already sorted list), it degrades to O(n²). Library sorts avoid this problem; Python's sorted() is O(n log n) in the worst case.

Loops that halve or double

A logarithm answers the question "how many times can I halve n before I reach 1?" For n = 8 the answer is 3 (8 → 4 → 2 → 1), so log₂ 8 = 3. Any loop that halves its counter, or doubles it up to n, runs about log₂ n times, so it is O(log n).

python
def halvings(n):
    steps = 0
    while n > 1:
        n //= 2
        steps += 1
    return steps

print(halvings(8), halvings(1000), halvings(10**6))   # Output: 3 9 19
Even a million needs only 19 halvings. That is why binary search is so fast.

Compare that with i += 2, which only takes n/2 steps: still O(n). Only multiplying or dividing the counter gives a logarithm.

python
i = 1
count = 0
while i < 1000:
    i *= 2
    count += 1
print(count)   # Output: 10
Doubling from 1 passes 1000 after 10 steps (2¹⁰ = 1024).

Recursion trees

A recurrence is an equation that describes the cost of a recursive function in terms of smaller calls. T(n) means "the time for input size n". To solve one, draw a recursion tree: each call is a box, its children are the calls it makes. Add up the work done on each level, then multiply by (or add up over) the number of levels.

  • T(n) = T(n/2) + 1, like binary search: one call per level, constant work each, and n halves until 1, so about log₂ n levels. Total O(log n).
  • T(n) = 2T(n/2) + n, like merge sort: level 0 has one call doing n work, level 1 has two calls doing n/2 each (n in total), level 2 has four calls doing n/4 each (n again). Every level does n work and there are log₂ n levels, so O(n log n).
  • T(n) = 2T(n − 1) + 1, like naive Fibonacci or Tower of Hanoi: the number of calls doubles at every level and there are n levels, so about 2ⁿ calls: O(2ⁿ).

The Master theorem is a shortcut for recurrences of the form T(n) = a·T(n/b) + f(n): a calls, each on 1/b of the input, plus f(n) work to split and combine. Compare f(n) with n^(log_b a). If f(n) is clearly smaller, the answer is n^(log_b a); if they are the same size, multiply by log n; if f(n) is clearly bigger, the answer is f(n). For binary search, a = 1 and b = 2 give n⁰ = 1, the same as f(n) = 1, so O(log n). For merge sort, a = 2 and b = 2 give n¹, the same as f(n) = n, so O(n log n). You rarely need more than this in an interview.

Common mistakes

  • Saying "two nested loops, so O(n²)" without checking how far the inner loop runs. Fix: if the inner loop halves or doubles, it is O(n log n); look at the exact bounds.
  • Treating i += 2 or i += 100 as logarithmic. Fix: adding a constant is still linear; only multiplying or dividing gives log n.
  • Thinking naive recursive Fibonacci is O(n) because n goes down by 1. Fix: each call makes two calls, so the tree doubles at each level: O(2ⁿ).
  • Quoting the average case as if it were guaranteed. Fix: say both, for example "O(n log n) on average, O(n²) in the worst case".
  • Forgetting work done outside the recursive calls, such as slicing or merging. Fix: that is the f(n) part, and it changes the answer.

Need help?

Stuck on a word or an idea? Search this course, or every course, and jump straight to the explanation or a worked question.

Try:

Watch the lesson

Best, average and worst case; analysing loops and recursion · 1/8

Cases, loops and recursion

This lesson is about cases, halving loops and recursion trees: the tools for working out the cost of any code you write.

Prefer a video by a teacher? Find free videos on “recursion tree method master theorem time complexity explained” on YouTube ↗

In real life: Guessing a roll number in a sorted register

A college office keeps a printed register of 1,000 students sorted by roll number. A clerk who turns pages one by one may need 1,000 looks in the worst case. A clerk who opens the middle and keeps halving needs at most 10 looks, because 2¹⁰ = 1,024 is more than 1,000. The best case for both is one look; the worst case is what decides who goes home on time.

python
def looks_needed(n):
    looks = 0
    while n > 0:
        n //= 2
        looks += 1
    return looks

print(looks_needed(1000))      # halving clerk, worst case
print(looks_needed(1000000))
Shows 10, then 20: a million entries need only 20 looks.

Practice (0/3 done)

Task 1 · Code · Python

Write count_halvings(n) for a whole number n ≥ 1. It returns how many times you can do n = n // 2 before n becomes 1. This equals floor(log₂ n). Use a loop, not the math module. Example: count_halvings(8) returns 3 and count_halvings(10) returns 3.

Task 2 · Code · Python

A guessing game: a secret whole number lies between low and high (both included). Each guess is the middle, (low + high) // 2. If the guess is too small, set low = guess + 1; if too big, set high = guess - 1. Write guess_steps(low, high, secret) that returns how many guesses are made, counting the correct one. Assume low ≤ secret ≤ high. Example: guess_steps(1, 100, 50) returns 1.

Task 3 · Question

What is the time complexity of this code? i = n while i > 0: for j in range(i): work() # O(1) i //= 2

Quick quiz

Question 1

1.Linear search for a target that is the first item takes one step. Which case is that?

Question 2

2.What does a loop "while i < n: i *= 3" cost, starting at i = 1?

Question 3

3.T(n) = 2T(n/2) + n. What is T(n)?

Question 4

4.Naive recursive Fibonacci, fib(n) = fib(n − 1) + fib(n − 2), grows about how fast?

Question 5

5.Quicksort with a pivot that is always the smallest item does what?

Read more (free)

Open a question to see the answer and why. The first ones are this topic's own quiz, so try the quiz above before you peek.

Linear search for a target that is the first item takes one step. Which case is that?

Answer: Best case

The input that makes the code do the least work is the best case.

What does a loop "while i < n: i *= 3" cost, starting at i = 1?

Answer: O(log n)

Multiplying by 3 reaches n after about log₃ n steps, which is O(log n).

T(n) = 2T(n/2) + n. What is T(n)?

Answer: O(n log n)

Every level of the recursion tree does n work and there are log n levels.

Naive recursive Fibonacci, fib(n) = fib(n − 1) + fib(n − 2), grows about how fast?

Answer: Exponentially, about O(2ⁿ)

Each call makes two more calls, so the tree roughly doubles at each level.

Quicksort with a pivot that is always the smallest item does what?

Answer: Degrades to O(n²)

Each split removes only one item, so there are n levels of up to n work each.

What is the WORST-case time of merge sort?Related

Answer: O(n log n)

It always splits in half and merges n items per level, whatever the input.

From Merge sort and quick sort

What is the base case in a recursive function?Related

Answer: The case simple enough to answer without calling the function again

The base case stops the recursion by answering directly.

From Recursion and sorting algorithms

Why pass lo and hi instead of slicing nums[mid + 1:] in recursive binary search?Related

Answer: Slicing copies the half, adding O(n) work and memory

A slice copies about n/2 items, so the search would cost O(n) instead of O(log n).

From Divide and conquer

A loop inside a loop, both over the same list of n items, is usually...Related

Answer: O(n^2)

For each of the n items, the inner loop does n steps: n times n.

From Big-O and searching

Pass the quiz (60% or more) or mark this topic as done when you are ready.

Your progress is saved on this device only.

Next steps

  1. 1 more topic in “Thinking in Big-O” before its exam: The real cost of Python and JavaScript built-ins.
  2. Module 1 exam: Thinking in Big-O · 14 questions · 25 min, without help. 5 topics of this module still to finish first.
  3. Then the next module, Arrays and strings. 61 topics left in the course before the final exam.

← Previous: Space complexity and amortised cost