DSA Notes
Learn how to perform best, average, and worst case analysis for any algorithm. Understand why input matters, how to identify the best and worst inputs for common algorithms, and when each type of analysis is most useful — with detailed worked examples for searching, sorting, and more.
Introduction
When we say an algorithm is O(n log n), we are almost always referring to its worst case performance. But the worst case is only one part of the story. Real programs run on real inputs — and real inputs are not always the worst case.
Understanding the three cases of algorithm analysis — best case, average case, and worst case — allows you to:
- Choose the right algorithm for your specific data distribution
- Understand why certain algorithms outperform others on real-world data
- Make informed decisions about when to use worst-case-optimal algorithms vs. average-case-optimal ones
- Answer interview questions with depth and precision
This lesson explores all three cases in detail, with complete worked examples for major algorithms.
Analysis of Key Algorithms
Algorithm 1 — Linear Search
Code:
Identifying the three cases:
Best Case: The algorithm terminates as early as possible — when the target is found at the very first position.
| Input | array = [42, 7, 19, 3, 88, ...], target = 42 |
| Step 1: array[0] = 42 = target | return 0 immediately |
| Operations | 1 comparison |
| Best case | Ω(1) |
Worst Case: The algorithm must examine every element — either the target is not present, or it is in the last position.
| Input | array = [7, 19, 3, 88, 42], target = 99 (not present) |
| Steps | Compare 7, 19, 3, 88, 42 — none match |
| Operations | n comparisons |
| Worst case | O(n) |
Average Case: Assuming the target is equally likely to be at any position (or absent):
Let p = probability the target is present = 0.5 (a common assumption)
Summary:
| Case | Input | Comparisons | Notation |
|---|---|---|---|
| Best | Target at index 0 | 1 | Ω(1) |
| Average | Target at random position | ~3n/4 | Θ(n) |
| Worst | Target absent or last | n | O(n) |
Algorithm 2 — Binary Search
Code:
Best Case: The target is the middle element of the array — found on the first comparison.
| array[3] = 7 = target | return immediately |
| Operations | 1 comparison |
| Best case | Ω(1) |
Worst Case: The target is not present, and the search reduces the array to a single element before failing.
| After k comparisons | n/2^k elements remain |
| Maximum comparisons | ⌊log₂(n)⌋ + 1 |
| For n = 8: max 4 comparisons (8 | 4 → 2 → 1 → empty) |
| Worst case | O(log n) |
Average Case: Expected number of comparisons when target is uniformly distributed:
For a sorted array of n elements, the average number of comparisons is approximately log₂(n) - 1.
This is because in a balanced binary search tree, the average depth of a node is log n - O(1).
Average case: Θ(log n)
Summary:
| Case | Input | Comparisons | Notation |
|---|---|---|---|
| Best | Target is middle element | 1 | Ω(1) |
| Average | Target at random position | log n - 1 | Θ(log n) |
| Worst | Target absent | log n + 1 | O(log n) |
Important observation: Binary Search's best case is O(1) but its worst and average cases are Θ(log n). This is different from Linear Search, which has O(1) best but O(n) worst and Θ(n) average.
Algorithm 3 — Bubble Sort
Code:
Best Case: The array is already sorted. The first pass finds no swaps and terminates immediately.
| Input | [1, 2, 3, 4, 5] |
| Pass 1: No swaps occur | swapped remains False → break |
| Operations | n-1 comparisons (one pass to verify sorted) |
| Best case | Ω(n) |
Note: Without the swapped optimization, Bubble Sort is always O(n²) even for sorted input. The optimization is what creates the Ω(n) best case.
Worst Case: The array is reverse sorted. Every element must bubble to its correct position.
| Input | [5, 4, 3, 2, 1] |
| Pass 1 | 4 swaps (5 moves from position 0 to position 4) |
| Pass 2 | 3 swaps (4 moves from position 0 to position 3) |
| Pass 3 | 2 swaps |
| Pass 4 | 1 swap |
| Total swaps | 4+3+2+1 = 10 = n(n-1)/2 |
| Operations | O(n²) |
| Worst case | O(n²) |
Average Case: For random permutations, on average n(n-1)/4 swaps are needed.
Average case: Θ(n²)
Summary:
| Case | Input | Operations | Notation |
|---|---|---|---|
| Best | Already sorted | n-1 comparisons | Ω(n) |
| Average | Random permutation | ~n²/4 swaps | Θ(n²) |
| Worst | Reverse sorted | n(n-1)/2 swaps | O(n²) |
Algorithm 4 — Insertion Sort
Insertion Sort is arguably the most interesting example because the difference between its best and worst cases is dramatic — and understanding this difference explains a key design decision in Python's built-in sorting algorithm.
Code:
Best Case — Already Sorted Input:
| Input | [1, 2, 3, 4, 5] |
| i=1 | key=2. array[0]=1. Is 1 > 2? No. Place 2 at position 1. 0 shifts. |
| i=2 | key=3. array[1]=2. Is 2 > 3? No. Place 3 at position 2. 0 shifts. |
| i=3 | key=4. array[2]=3. Is 3 > 4? No. Place 4 at position 3. 0 shifts. |
| i=4 | key=5. array[3]=4. Is 4 > 5? No. Place 5 at position 4. 0 shifts. |
| Total comparisons | n-1 (one per outer loop iteration — just checking the immediate left neighbor) |
| Total shifts | 0 |
| Best case | Ω(n) |
Worst Case — Reverse Sorted Input:
| Input | [5, 4, 3, 2, 1] |
| i=1 | key=4. Need to shift 5. 1 shift. |
| i=2 | key=3. Need to shift 5, 4. 2 shifts. |
| i=3 | key=2. Need to shift 5, 4, 3. 3 shifts. |
| i=4 | key=1. Need to shift 5, 4, 3, 2. 4 shifts. |
| Total shifts | 1 + 2 + 3 + 4 = n(n-1)/2 |
| Worst case | O(n²) |
Average Case: For random input, each element has to shift past approximately half the sorted portion on average.
Expected shifts for element at position i ≈ i/2
Total expected shifts = Σ i/2 from i=1 to n-1 = n(n-1)/4
Average case: Θ(n²)
Why This Matters — TimSort:
Python's built-in sorted() and list.sort() use TimSort, a hybrid algorithm created by Tim Peters. TimSort runs on the key insight that:
- Real-world data often contains runs — subsequences that are already sorted (or nearly sorted)
- Insertion Sort's Ω(n) best case makes it ideal for processing already-sorted runs
- Merge Sort is used to merge the sorted runs efficiently
By splitting the data into runs and applying Insertion Sort to small/sorted portions, TimSort achieves near-O(n) performance on nearly sorted data while maintaining O(n log n) worst case.
Summary:
| Case | Input | Shifts | Notation |
|---|---|---|---|
| Best | Already sorted | 0 | Ω(n) |
| Average | Random permutation | n²/4 | Θ(n²) |
| Worst | Reverse sorted | n(n-1)/2 | O(n²) |
Algorithm 5 — Quick Sort
Quick Sort's three cases have the most dramatic spread of any common algorithm. Understanding them is essential for any serious programmer.
Code:
Best Case — Balanced Partitions:
The pivot always splits the array into two equal halves.
| Level 0: 1 partition of n | n work |
| Level 1: 2 partitions of n/2 | n work total |
| Level 2: 4 partitions of n/4 | n work total |
| Level log n: n partitions of 1 | n work total |
| Total | n work × log n levels = n log n |
| Best case | Ω(n log n) |
Worst Case — Worst Pivot Selection:
The pivot is always the smallest or largest element, creating maximally unbalanced partitions.
| Input | [1, 2, 3, 4, 5] (sorted), pivot = last element = 5 |
| Step 1 | Partition [1,2,3,4|5]. Left = [1,2,3,4], Right = []. 4 comparisons. |
| Step 2 | Partition [1,2,3|4]. Left = [1,2,3], Right = []. 3 comparisons. |
| Step 3 | Partition [1,2|3]. Left = [1,2], Right = []. 2 comparisons. |
| Step 4 | Partition [1|2]. Left = [1], Right = []. 1 comparison. |
| Total | 4+3+2+1 = n(n-1)/2 comparisons |
| Worst case | O(n²) |
This is why choosing "last element as pivot" on an already-sorted array is disastrous.
Fix — Randomized Quick Sort:
By choosing a random pivot, the worst case remains O(n²) in theory (you could be unlucky every time), but the probability of this occurring becomes astronomically small.
Average Case:
For random pivot selection (or random input):
The expected number of comparisons is approximately 2n ln(n) ≈ 1.39 × n log₂(n).
Average case: Θ(n log n)
Summary:
| Case | Input | Comparisons | Notation |
|---|---|---|---|
| Best | Pivot always splits perfectly | n log n | Ω(n log n) |
| Average | Random pivot/random input | 1.39 n log n | Θ(n log n) |
| Worst | Sorted + bad pivot | n(n-1)/2 | O(n²) |
Why Use Quick Sort Despite O(n²) Worst Case?
- Average case is Θ(n log n) — same as Merge Sort
- Better constant factors than Merge Sort (no auxiliary array copying)
- Better cache performance (accesses contiguous memory)
- Randomized version makes worst case practically impossible
- In practice, Quick Sort runs faster than Merge Sort for most inputs
This is why C++, Java, and Python all use Quick Sort variants (often hybrid) for general-purpose sorting.
Algorithm 6 — Merge Sort
Merge Sort is the "safe" choice because its three cases are all the same.
Best Case: Θ(n log n) Average Case: Θ(n log n) Worst Case: O(n log n)
All cases: Θ(n log n)
Merge Sort always divides the array in half (creating log n levels) and always merges (doing n work per level). There is no short circuit, no early termination, no lucky input. It always does the same amount of work.
This predictability makes Merge Sort the choice when you need guaranteed performance.
Comparing Algorithms by Case
| Algorithm | Best (Ω) | Average (Θ) | Worst (O) | Stable | Space |
|---|---|---|---|---|---|
| Linear Search | Ω(1) | Θ(n) | O(n) | Yes | O(1) |
| Binary Search | Ω(1) | Θ(log n) | O(log n) | Yes | O(1) |
| Bubble Sort | Ω(n) | Θ(n²) | O(n²) | Yes | O(1) |
| Selection Sort | Ω(n²) | Θ(n²) | O(n²) | No | O(1) |
| Insertion Sort | Ω(n) | Θ(n²) | O(n²) | Yes | O(1) |
| Merge Sort | Ω(n log n) | Θ(n log n) | O(n log n) | Yes | O(n) |
| Quick Sort | Ω(n log n) | Θ(n log n) | O(n²) | No | O(log n) |
| Heap Sort | Ω(n log n) | Θ(n log n) | O(n log n) | No | O(1) |
Choosing an Algorithm Based on Case Analysis
| Scenario | Best Choice | Reason |
|---|---|---|
| Nearly-sorted data | Insertion Sort | Ω(n) best case, fast on real-world sequences |
| Guaranteed worst-case performance | Merge Sort or Heap Sort | Θ(n log n) always |
| Average case performance (random data) | Quick Sort (randomized) | Θ(n log n) with best constant factors |
| Small arrays (n < 20) | Insertion Sort | Low constant overhead |
| Searching sorted data | Binary Search | Θ(log n) average and worst |
| Searching unsorted data | Linear Search → Hash Map | Linear Search O(n) or O(1) with hash |
Summary
- Worst case (Big O): The maximum operations for any input of size n. Always the primary concern for safety guarantees.
- Best case (Big Omega): The minimum operations for the most favorable input. Useful for identifying optimization opportunities and understanding hybrid algorithms.
- Average case (Big Theta): Expected operations over all inputs. Most relevant for practical performance estimation.
- The gap between best and worst case reveals opportunities: large gaps (like Insertion Sort's Ω(n) vs. O(n²)) suggest the algorithm adapts to input structure — a feature that hybrid algorithms exploit.
- Always ask: "Is my real-world input closer to the best case or worst case?" This determines which algorithm is appropriate.
*Next Lesson: Space Complexity — Analyzing memory usage with the same rigor we have applied to time complexity.*
Exam Focus
Revise definitions, diagrams, examples, and short-answer points for Best, Average, and Worst Case Analysis — A Practical Guide for Every Algorithm.
Interview Use
Prepare one clear explanation, one practical example, and one common mistake for this Data Structures & Algorithms topic.
Search Terms
data-structures-algorithms, data structures & algorithms, data, structures, algorithms, algorithm, analysis, best
Related Data Structures & Algorithms Topics