Skip to content
Ameen Abozaid
PublishedBEG-017-L06

Counting steps: a first look at efficiency

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

Lesson Overview

You now have two searches and two sorts. Which is "better"? In the first lesson we said: correct first, then how many steps. This lesson makes the second part concrete. We add a counter to each algorithm, run it on inputs of growing size, and look at how the counts change when the data gets bigger.

The goal is an informal feeling for growth: when n doubles, does the work double, barely change, or quadruple? This is a preview. A later module formalises the idea with proper notation, so here we only count and compare.

Why this matters: a feature that works with 100 test records can crawl with a million real ones. Counting steps lets you predict that before you ship.

This lesson builds on all four algorithms of this module (linear search, binary search, bubble sort, selection sort), and on static counter fields and methods.

Learning Objectives

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

  • Add a counter to an algorithm and increase it at each comparison.
  • Choose what to count (the comparison inside the innermost loop) and say why.
  • Produce a table of counts for n = 10, 20, and 40, and larger sizes.
  • Describe growth in words: doubles, adds one step, or quadruples.
  • Rank linear search, binary search, bubble sort, and selection sort by growth.
  • Explain why counting steps is more reliable than timing for comparing algorithms.
  • Say what we do not yet know: the formal notation comes later.

Written Explanation

What to count

Computers differ in speed, so "it took 3 milliseconds" depends on the machine. A count of steps does not. We pick the operation the algorithm spends its time on, usually the comparison in the innermost loop, and count how often it runs. Other work (increasing i, swapping) is roughly proportional, so it does not change the picture.

The counter is a plain long that every algorithm increments each time it compares. A long is used because counts for large inputs can pass what an int holds comfortably.

java
static long count;

static int linearSearch(int[] a, int target) {
    for (int i = 0; i < a.length; i++) {
        count++;
        if (a[i] == target) return i;
    }
    return -1;
}

The same count++; goes next to the comparison in the other three methods: before the if that tests a[mid] in binarySearch (inside the loop, counting one per look), and before the if (a[i] > a[i + 1]) of bubble sort, and before the if (a[j] < a[min]) of selection sort. Reset count = 0; before each run.

The experiment

For each size n we use a sorted array {0, 2, 4, ...} and search for the value -1, which is not present and smaller than everything (a missing target forces the search to run to the end). For the sorts we give the reversed array {n, n-1, ..., 1}. These are the demanding inputs.

Real output from running the experiment on JDK 21:

nlinearbinarybubbleselection
101034545
20204190190
40405780780
1,0001,0009499,500499,500
1,000,0001,000,00019not runnot run

The sorts were not run on a million elements: that would be about 500 billion comparisons, which would take a very long time. That fact is itself the lesson.

The binary column counts the loop iterations when the target is smaller than every element. A missing target that falls elsewhere can take one more look at most, so the true worst case for 1,000 elements is 10; with this input it was 9.

Reading the table: how growth looks

Look at what happens each time n doubles from 10 to 20 to 40.

Linear search: 10, 20, 40. The count doubles each time. Work grows in step with the data. A million elements need a million comparisons.

Binary search: 3, 4, 5. The count goes up by one each time the data doubles. From 1,000 to 1,000,000 (a thousand times more data) it grows only from 9 to 19, which is about 10 more looks. This is the strongest effect in the table.

Bubble and selection sort: 45, 190, 780. Each time n doubles the count multiplies by a little over 4 (190 / 45 is about 4.2, and 780 / 190 is about 4.1). The count is n × (n - 1) / 2, and when n doubles, n × n becomes four times larger, so doubling the data roughly quadruples the work. For 1,000 elements it is already 499,500 comparisons.

Summary in words:

AlgorithmWhen n doubles, work...Growth
Binary searchgoes up by one stepvery slow
Linear searchdoublessteady, proportional
Bubble sort, selection sortabout quadruplesfast, like n × n

Ranked from slowest-growing to fastest-growing: binary search, linear search, then the two sorts. This ordering is what matters for the data sizes real services meet.

Why not just time it?

Timing is useful (the next lesson does it) but it is noisy: it changes with the machine, other running programs, and how the Java runtime warms up. Counts are exact and repeatable, and they explain the trend. Use counts to understand an algorithm and timings to confirm that your real program behaves as expected.

Be honest about the limits

  • We counted one operation. Real cost includes memory access, swaps, and more.
  • We tested one kind of input. The comparison count of linear search depends on where the target is, as you saw earlier. Selection sort's count did not change with the input, while bubble sort with an early exit would change.
  • Small n can mislead. For n = 10 the difference between 10 and 3 looks small. It becomes decisive only for large data.
  • Formal ways of describing growth exist (the topic of BEG-039 in the course plan). Here we only use the words "doubles", "adds one", and "quadruples".

Examples

Example 1: counting a linear search by hand (simple)

Array of 8 elements, target missing. The loop compares a[0] through a[7], so 8 comparisons. Double the array to 16 and the count becomes 16.

java
int[] data = {14, 3, 27, 9, 3, 18};
countedSearch(data, 99);   // counter: 6
countedSearch(data, 14);   // counter: 1

These are the worst and best cases for 6 elements: 6 and 1.

Example 2: how few looks for a big array (practical)

For a sorted array of 1,000,000 elements and a missing target, the experiment counted 19 loop iterations in binary search and 1,000,000 in linear search.

text
linear:  1000000
binary:  19

That is about 52,000 times fewer comparisons, and the gap keeps widening as the array grows. Searching a sorted collection many times is exactly where binary search pays off.

Example 3: a sorting counter on small input, by hand (realistic)

Count bubble sort's comparisons on 4 elements without running anything. Passes compare 3, 2, and 1 pairs: 3 + 2 + 1 = 6. For 5 elements it is 10; for 8 elements it is 7 + 6 + 5 + 4 + 3 + 2 + 1 = 28. Check against the formula n × (n - 1) / 2: for 8 it gives 8 × 7 / 2 = 28.

Now predict, then check. For n = 100 the formula gives 100 × 99 / 2 = 4,950; for n = 200 it gives 19,900. Dividing, 19,900 / 4,950 is about 4.0. The prediction "double the data, quadruple the work" holds without running the code.

Backend Insight

Backend code has to survive growth: today 500 users, next year 500,000. Counting steps is the habit that tells you which parts will hurt first:

  • A loop over all users to find one (linear) is fine at 500 and wasteful at 500,000 if done on every request.
  • A nested loop comparing every user with every other user (about n × n / 2) goes from about 125,000 comparisons at 500 users to about 125 billion at 500,000. Doubling the users quadruples the work, and a thousand times more users multiply it by a million.
  • Structures that find things by halving or by jumping straight to the place (sorted arrays, indexes, and the hash-based collections you will meet later) keep the work almost flat as data grows.

When you review code, look at the loops and their nesting, and ask: if this data became 100 times bigger, what happens to the work? That one question catches a large share of performance problems before they reach production.

Common Mistakes

Mistake 1: Counting the wrong thing

Placing count++ outside the innermost loop (say, once per pass in bubble sort) counts passes, not comparisons, and the growth looks wrong. Count the operation inside the innermost loop.

Mistake 2: Forgetting to reset the counter

A counter that is not reset before the second run adds the results of two runs. Set count = 0; before each run.

Mistake 3: Comparing counts from different inputs

Counting linear search on a present target and binary search on a missing one tells you nothing. Compare like with like: the same size and the same kind of case (best, worst, typical).

Mistake 4: Judging by small n

At n = 10 the difference between 10 and 3 or between 45 and 10 looks small. Always ask what happens as n grows, and test sizes that are far apart.

Mistake 5: Using int for big counts

A count of quadrillions would overflow an int and print wrong, even negative, numbers. Use long. (Counts in this lesson stay small, but the habit protects you.)

Exercises

Try each exercise yourself before opening the solutions.

Level A: Foundation

A1. If linear search needs 50 comparisons in the worst case for some n, how many for 2n?

Show solution A1

A1. 100. Linear search work doubles when n doubles.

A2. Binary search needs 10 looks for some array. About how many for an array twice as big?

Show solution A2

A2. About 11, one more look.

A3. Using n × (n - 1) / 2, how many comparisons does bubble sort make on 6 elements?

Show solution A3

A3. 6 × 5 / 2 = 15.

Level B: Application

B1. Predict, without running code, the number of comparisons bubble sort makes on 10 and 20 elements. Then compute the ratio.

Show solution B1

B1. For 10: 10 × 9 / 2 = 45. For 20: 20 × 19 / 2 = 190. The ratio is 190 / 45, about 4.2, which is close to 4.

B2. Insert count++ in the right place in selectionSort. State the line it goes before.

Show solution B2

B2. Put count++; inside the inner loop, immediately before if (a[j] < a[min]) min = j;, so it runs once per comparison.

B3. Explain why timing a program once is a poor way to compare two algorithms, in two sentences.

Show solution B3

B3. One timing is affected by the machine, other programs, and how warmed up the Java runtime is, so two runs of the same code can differ. A step count is exact and repeatable and shows how the work grows.

Level C: Challenge

C1. Write a main that prints a table of counts for linear search (missing target) and bubble sort (reversed array) for n = 10, 20, 40, 80. Describe the growth you see in each column in one sentence.

Show solution C1

C1. A sample program, followed by the output pattern you should get:

java
static long count;

public static void main(String[] args) {
    for (int n : new int[]{10, 20, 40, 80}) {
        int[] a = new int[n];
        for (int i = 0; i < n; i++) a[i] = i * 2;
        count = 0; linearSearch(a, -1); long lin = count;
        int[] r = new int[n];
        for (int i = 0; i < n; i++) r[i] = n - i;
        count = 0; bubbleSort(r); long bub = count;
        System.out.println(n + " | " + lin + " | " + bub);
    }
}

Expected counts: linear 10, 20, 40, 80 and bubble 45, 190, 780, 3160. The first column doubles each row; the second multiplies by about four each row. I ran this program and it printed exactly these counts (3,160 also equals 80 × 79 / 2).

C2. A teammate proposes to run bubble sort on 10 million records "because it is simple". Use the formula to estimate the number of comparisons, and explain whether it is realistic. Do not run it.

Show solution C2

C2. With n = 10,000,000 the formula gives about 10,000,000 × 9,999,999 / 2, roughly 5 × 10^13 comparisons. Even at a billion comparisons per second that is on the order of fifty thousand seconds, about 14 hours, and the speed estimate is only a rough illustration. It is not realistic. A library sort is far faster, and the choice of algorithm matters for large data.

Mini Challenge

Take the four methods from this module and add counters to them. Produce a table with n = 8, 16, 32, 64 for: linear search (missing target), binary search (missing target below everything), bubble sort (reversed array), and selection sort (reversed array). For each column write one sentence: "when n doubles, the work ...". Finish with a short paragraph (about five sentences) explaining which algorithm you would use for a million sorted records searched many times, and why.

Check your work: the linear column doubles, the binary column grows by about one per row, both sorts roughly quadruple, and your paragraph mentions the sorted requirement.

Summary & Next Lesson

Key takeaways

  • To compare algorithms, count the key operation (the comparison in the innermost loop) for growing n, using a counter that you reset before each run.
  • Linear search: double the data, double the work. Binary search: double the data, one more step. Bubble and selection sort: double the data, about four times the work.
  • Counts are exact and repeatable. Timing is noisy but confirms real behaviour.
  • Small inputs hide differences. The gap matters when the data is large.
  • This is an informal preview: formal notation for growth comes in a later module.

Next lesson

The next lesson, "Demo: racing linear and binary search", measures both searches with real timings on arrays of 1,000, 100,000, and 10,000,000 elements, which is also the setup for this module's lab.

Self-check

  1. What do we count to compare two sorting algorithms?
  2. When n doubles, what happens to linear search work?
  3. When n doubles, what happens to bubble sort work?
  4. About how many looks does binary search need for 1,000,000 sorted elements?
  5. Why is counting steps more reliable than timing for comparing algorithms?
Show answers
  1. The comparisons in the innermost loop.
  2. It roughly doubles.
  3. It roughly quadruples.
  4. About 20 (the experiment counted 19 for a target below all elements).
  5. Counts do not depend on the machine or on other programs and are exactly repeatable; timings vary.

References

  • Oracle, Arrays (Java SE 21 API). Its sort documentation states O(n log(n)) performance for the library sort as an implementation note, which supports the remark that library sorts are far better than n × n methods for large data.
  • Joshua Bloch, Extra, Extra - Read All About It. Cited here only for the scale of arrays (about a billion elements) at which halving-based algorithms and their overflow bug become relevant.
  • All counts in the table come from running the code on JDK 21. The time estimate in C2 is an illustration based on an assumed speed, not a measurement.

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.