Skip to content
Ameen Abozaid
PublishedBEG-039-L05

Best, average, worst

  • Lesson 5 of 6
  • 11 min read
  • Beginner
On This Lesson 11 topics

Lesson Overview

Until now each algorithm had one Big O. But the same method, run on two inputs of the same size, can do very different amounts of work. Linear search finishes in one step if the target is first, and in n steps if it is last or missing. This lesson names these situations: best case, worst case, and average case, and explains which one to quote and why.

Why this matters: "it is O(1)" and "it is O(n)" can both be true statements about the same code, about different inputs. If you do not say which case you mean, you will be misunderstood in a design discussion or an interview.

This lesson builds on Big O (BEG-039-L02), on analyzing loops (BEG-039-L03), and on linear search, binary search, and bubble sort with its early exit (BEG-017).

Learning Objectives

By the end of this lesson you will be able to:

  • Define best case, worst case, and average case.
  • Identify the input that causes each case for linear search, binary search, and bubble sort with an early exit.
  • Give a separate Big O for each case.
  • Explain why the worst case is the one usually quoted, and when the average case matters more.
  • Explain "amortized" and "average-case" in the documentation of ArrayList.add and HashMap.
  • Avoid confusing "Big O" with "worst case".

Written Explanation

A case is a kind of input

For an input size n, many inputs are possible. A case is a kind of input of that size:

  • Best case: the luckiest input of size n, where the algorithm does the least work.
  • Worst case: the unluckiest input of size n, where it does the most work.
  • Average case: a typical input, averaged over the inputs you expect.

Each case has its own step count and so its own Big O. The case is a statement about the input, never about the machine or the programmer's luck.

Linear search

Take the array {7, 3, 9, 1}, with the search from BEG-017 counting one step per element read. I ran it for four targets:

text
target 7 index 0 steps 1
target 9 index 2 steps 3
target 1 index 3 steps 4
target 5 index -1 steps 4
  • Best case: the target is the first element: 1 step, for any n. That is O(1).
  • Worst case: the target is last or not present: n steps. That is O(n).
  • Average case: if the target is present and equally likely to be anywhere, the average of 1, 2, 3, and 4 steps is 2.5, which matches (n + 1) / 2. It is about half of n, which is still O(n), because the half is a constant. The program printed average over present targets: 2.5.

The average case needs an assumption about which inputs are likely. Here: every position is equally likely. Change the assumption (for example, targets are usually at the front) and the average changes. That is why average-case claims always come with a stated assumption.

Binary search

For a sorted array of 8 elements {1, ..., 8}, counting one step per look:

text
target 4: 1 look   (the middle element, best case)
target 8: 4 looks  (the last element, worst case among present values)
target 0: 3 looks  (missing, smaller than everything)

Best case: O(1), when the first middle is the target. Worst case: O(log n), about log₂ n + 1 looks. Binary search is usually quoted as O(log n), the worst case.

Bubble sort with an early exit

BEG-017's bubble sort stops when a full pass makes no swap. Counting comparisons for n = 1,000:

text
bubble sorted input: 999, reversed: 499500
  • Best case: the data is already sorted, so one pass of n - 1 = 999 comparisons finds no swap and the loop ends: O(n).
  • Worst case: the data is in reverse order: n × (n - 1) / 2 = 499,500 comparisons: O(n²).

Notice that the best case of one algorithm (O(n)) can be a lot better than its worst (O(n²)).

Which case to quote

Usually the worst case, for three reasons:

  1. It is a guarantee: no input of size n will cost more. That is what you need when you promise a response time.
  2. It needs no assumption about what the inputs look like.
  3. The best case is too optimistic to plan with. "It can be O(1)" says almost nothing if the usual case is O(n).

The average case is the right figure when the worst input is rare and you really do process many inputs: its value depends on the assumption you made. Always say which one you are quoting. Big O is a way of writing the growth; it does not mean "worst case". You can write the Big O of the best case, of the worst case, or of the average case.

When documentation says "average" or "amortized"

Two phrases you have already seen in the Java documentation:

  • Average-case constant time. The HashSet and HashMap documentation promises constant-time basic operations assuming the hash function disperses the elements properly among the buckets. The HashMap page also warns that many keys with the same hashCode() is a sure way to slow a hash table down. So the constant-time figure is the average (typical) case under a good hash function; a bad hashCode can push a lookup toward the worst case. This is the same hashCode you wrote in BEG-026.
  • Amortized constant time. The ArrayList documentation says add runs in amortized constant time: adding n elements takes O(n) in total. Occasionally an add takes longer, because (as I understand it, and the page I read does not spell this out) the list has to grow its internal storage and copy; spread over many adds, the average cost per add stays constant. "Amortized" means averaged over a sequence of operations, with no assumption about random input.

A third example: the Arrays.sort(Object[]) documentation says it needs far fewer than n lg n comparisons when the input is partially sorted, and about n comparisons for nearly sorted input. The best case is clearly better than the typical one.

Examples

Example 1: classify the input (simple)

For a linear search in {12, 5, 8, 20, 3}, say which case each target causes.

TargetStepsCase
121best case
83in between (average-like)
35worst case (last)
995worst case (missing)

The array stays the same; only the target changes the case.

Example 2: the same sort on two inputs (practical)

You sort 1,000 numbers with the early-exit bubble sort. If they arrive from a nightly job already sorted, it does 999 comparisons. If they come in reverse order, it does 499,500, which is 500 times as many. A report that says "bubble sort took 2 ms in my test" may have tested the sorted case by accident. When you test an algorithm, choose inputs that trigger the worst case, and the best case, on purpose.

Example 3: choosing which guarantee you need (realistic)

An API must answer within a fixed time for every request. It looks up a user in a sorted array by ID using binary search. Best case O(1) and average-ish O(log n) are interesting, but the number to design with is the worst case, O(log n): even the unluckiest lookup in 1,000,000 users takes about 20 looks.

If the same API kept users in an unsorted ArrayList and called contains, the best case O(1) (the user is first) is of no help to the customer whose account is last. The worst case, O(n), sets the experience for the unluckiest requests, and under heavy traffic the unluckiest requests happen all the time.

Backend Insight

Service-level promises are worst-case or near-worst-case promises. Teams measure "the slowest 1% of requests" because the average hides the users who have a bad time. An algorithm with an excellent average and a terrible worst case, such as a hash table with a poor hashCode, can pass every test and then fail on the one input pattern real traffic produces.

Two review questions follow from this lesson:

  • What is the worst input for this code, and could real traffic produce it?
  • If the figure I am quoting is an average or amortized one, what is the assumption behind it?

Common Mistakes

Mistake 1: Quoting the best case

  • Incorrect: "Linear search is O(1)."
  • Correct: "Linear search is O(1) in the best case and O(n) in the worst case; we quote O(n)."

Mistake 2: Equating Big O with worst case

Big O is a notation for growth. You can give the Big O of any case; just say which.

Mistake 3: Quoting an average without its assumption

"Average O(n/2)" is meaningless unless you say what is being averaged: every position equally likely? Mostly early positions?

Mistake 4: Testing only the friendly input

If your tests only use the best case, the program may be 500 times slower in production, as in Example 2. Test the worst case on purpose.

Exercises

Try each exercise yourself before opening the solutions.

Level A: Foundation

A1. Define best, worst, and average case in one sentence each.

Show solution A1

A1. Best case: the input of size n where the algorithm does the least work. Worst case: the input where it does the most. Average case: the work on a typical input, averaged under a stated assumption.

A2. For linear search over {6, 2, 8, 4}, how many steps for target 6, target 4, and target 10?

Show solution A2

A2. Target 6: 1 step. Target 4: 4 steps (it is last). Target 10: 4 steps (missing, the whole array is read).

A3. Which case does a Big O of "O(n)" for linear search normally describe?

Show solution A3

A3. The worst case: the target is last or missing.

Level B: Application

B1. An algorithm takes 1 step if the first element is negative and n steps otherwise. Give its best-case and worst-case Big O, and the input that causes each.

Show solution B1

B1. Best case: O(1), when the first element is negative. Worst case: O(n), when it is not (including when no element is negative).

B2. Binary search on 16 sorted elements: what is the largest number of looks it can need for a present value? What is the smallest? Use "one look per halving" and the pattern from L01.

Show solution B2

B2. Largest: 5 looks (16 → 8 → 4 → 2 → 1 gives 4 halvings, plus the final look). Smallest: 1 look, if the first middle element is the target. So worst O(log n), best O(1).

B3. A colleague says "HashSet.contains is O(1), end of story." What would you ask them to add, using the documentation?

Show solution B3

B3. Ask for the assumption: the documentation's constant-time promise holds if the hash function disperses elements well. With many equal hashCode values the set can slow down.

Level C: Challenge

C1. Write static int countStepsToFind(int[] a, int target) that returns the number of elements a linear search reads. For the array {5, 1, 4, 2, 3}, write a small loop that calls it for every present target and computes the average steps. What does it equal in terms of n?

Show solution C1

C1.

java
static int countStepsToFind(int[] a, int target) {
    int steps = 0;
    for (int x : a) {
        steps++;
        if (x == target) return steps;
    }
    return steps;
}

Calling it for 5, 1, 4, 2, 3 gives 1, 2, 3, 4, 5, an average of 3, which is (n + 1) / 2 for n = 5.

C2. Your service handles 10,000 requests a second. An operation is O(1) on average but O(n) in the worst case, and the worst case happens once in 1,000 requests with n = 1,000,000. Is "O(1) on average" a good enough description for capacity planning? Argue briefly, with numbers.

Show solution C2

C2. The rare expensive case still dominates the total. Out of 10,000 requests per second, about 10 per second hit the worst case at 1,000,000 steps each: 10,000,000 extra steps per second, versus 10,000 steps for the other 9,990 requests. So the average cost per request is about 1,000 steps, not 1, and the slow requests will be noticeably slow for the unlucky users. "O(1) on average" is not enough; you must also know the worst case and its frequency.

Mini Challenge

Pick one algorithm from BEG-017 (linear search or the early-exit bubble sort). Using a step counter:

  1. Write down the input that causes the best case and the input that causes the worst case, for n = 100.
  2. Run the code on both inputs and record the counts.
  3. Write one sentence for each case with its Big O, and a final sentence saying which of the two you would quote when promising a response time, and why.

Check your work: your counts match the formulas (for linear search 1 and 100; for bubble sort 99 and 4,950), and your final sentence names a guarantee.

Summary & Next Lesson

Key takeaways

  • A case is a kind of input of size n: best, worst, or average. Each has its own Big O.
  • Linear search is O(1) best case and O(n) worst case. Binary search is O(1) best and O(log n) worst. Early-exit bubble sort is O(n) best and O(n²) worst.
  • We usually quote the worst case: it is a guarantee and needs no assumption.
  • Average case needs a stated assumption. Amortized means averaged over a sequence of operations.
  • Big O is notation for growth; it does not mean "worst case".

Next lesson

The next lesson, "Demo: turning an O(n²) solution into O(n)", puts the whole module to work: you will analyze a duplicate checker, find its hidden loop, replace it with a HashSet, and measure the speed-up.

Self-check

  1. In which case does linear search take one step?
  2. Why is the worst case usually the one quoted?
  3. What assumption lies behind "average-case O(1)" for HashSet.contains?
  4. What input gives early-exit bubble sort its best case?
  5. What does "amortized constant time" mean for ArrayList.add?
Show answers
  1. The best case: the target is the first element.
  2. It is a guarantee: no input of that size will cost more, and it needs no assumption about typical inputs.
  3. That the hash function disperses the elements properly among the buckets.
  4. Data that is already sorted, so the first pass makes no swap and the loop stops.
  5. The cost averaged over a sequence of adds is constant per add, even though a single add occasionally costs more.

References

  • Oracle, HashSet (Java SE 21 API) and HashMap (Java SE 21 API). Constant-time basic operations assuming the hash function disperses elements properly; the HashMap page warns that many keys with the same hashCode() slow the table down.
  • Oracle, ArrayList (Java SE 21 API). add runs in amortized constant time (adding n elements takes O(n)). The explanation that occasional growth and copying causes the rare slow add is my own understanding; the page I read does not state it.
  • Oracle, Arrays (Java SE 21 API). sort(Object[]) needs far fewer than n lg n comparisons on partially sorted input and about n on nearly sorted input.
  • Princeton University, Analysis of Algorithms (Algorithms, 4th edition). It discusses worst-case guarantees, input models, and amortized analysis. It does not treat best and average case directly.
  • Step counts shown were produced by running the code on JDK 21. The "1% slowest requests" remark in Backend Insight is general engineering practice, not a cited claim.

Practice

Apply this lesson with the module's lab, exercises and quiz.

Lab and exercises Quiz

Marking a lesson complete is optional and you can undo it any time. Your progress stays in this browser.

Continue to next lesson

Type to search levels, phases, modules and lessons.