Best, average, worst
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.addandHashMap. - 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:
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:
nsteps. 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 ofn, which is still O(n), because the half is a constant. The program printedaverage 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:
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:
bubble sorted input: 999, reversed: 499500- Best case: the data is already sorted, so one pass of
n - 1 = 999comparisons finds no swap and the loop ends: O(n). - Worst case: the data is in reverse order:
n × (n - 1) / 2 = 499,500comparisons: 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:
- It is a guarantee: no input of size
nwill cost more. That is what you need when you promise a response time. - It needs no assumption about what the inputs look like.
- 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
HashSetandHashMapdocumentation promises constant-time basic operations assuming the hash function disperses the elements properly among the buckets. TheHashMappage also warns that many keys with the samehashCode()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 badhashCodecan push a lookup toward the worst case. This is the samehashCodeyou wrote in BEG-026. - Amortized constant time. The
ArrayListdocumentation saysaddruns in amortized constant time: addingnelements takes O(n) in total. Occasionally anaddtakes 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.
| Target | Steps | Case |
|---|---|---|
| 12 | 1 | best case |
| 8 | 3 | in between (average-like) |
| 3 | 5 | worst case (last) |
| 99 | 5 | worst 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.
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:
- Write down the input that causes the best case and the input that causes the worst case, for
n= 100. - Run the code on both inputs and record the counts.
- 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
- In which case does linear search take one step?
- Why is the worst case usually the one quoted?
- What assumption lies behind "average-case O(1)" for
HashSet.contains? - What input gives early-exit bubble sort its best case?
- What does "amortized constant time" mean for
ArrayList.add?
Show answers
- The best case: the target is the first element.
- It is a guarantee: no input of that size will cost more, and it needs no assumption about typical inputs.
- That the hash function disperses the elements properly among the buckets.
- Data that is already sorted, so the first pass makes no swap and the loop stops.
- 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).
addruns in amortized constant time (addingnelements takes O(n)). The explanation that occasional growth and copying causes the rare slowaddis my own understanding; the page I read does not state it. - Oracle, Arrays (Java SE 21 API).
sort(Object[])needs far fewer thann lg ncomparisons on partially sorted input and aboutnon 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.
Marking a lesson complete is optional and you can undo it any time. Your progress stays in this browser.