Time and Space Complexity (Big-O) in Java
Suppose a teacher needs to find one student's answer sheet. If the sheets are in a random pile, the teacher may have to look at every sheet. If they are sorted by roll number, the teacher can open the pile in the middle and halve the search each time. With 40 sheets the difference is small. With 40,000 sheets, it is the difference between an afternoon and a few seconds. Big-O notation is the language programmers use to describe exactly this: how the work grows when the input grows. In this lesson, you will learn what time and space complexity mean, the common Big-O classes, and how to work out the Big-O of a piece of Java code.
What Is Big-O Notation?
Big-O notation in Java (and in computer science generally) is a way of describing how the number of steps an algorithm takes, or the memory it uses, grows as the input size n grows. It ignores exact timings, which depend on the computer, and focuses on the shape of the growth.
- Time complexity: how the number of steps grows with
n. - Space complexity: how the extra memory grows with
n(not counting the input itself).
Big-O usually describes the worst case: the most work the algorithm can need for an input of size n.
The Common Classes
| Big-O | Name | Steps for n = 1,000 | Java example |
|---|---|---|---|
O(1) | Constant | 1 | array[i], map.get(key) |
O(log n) | Logarithmic | about 10 | Binary search, TreeMap.get |
O(n) | Linear | 1,000 | Loop over an array, list.contains(x) |
O(n log n) | Linearithmic | about 10,000 | Merge sort, Arrays.sort on objects |
O(n²) | Quadratic | 1,000,000 | A loop inside a loop, bubble sort |
O(2ⁿ) | Exponential | more than the atoms in the universe | Trying every subset |
Counting Steps in Code
Let's see an example that counts how many times the innermost line runs for three common loop shapes.
public class Growth {
static long linear(int n) {
long steps = 0;
for (int i = 0; i < n; i++) {
steps++;
}
return steps;
}
static long nested(int n) {
long steps = 0;
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
steps++;
}
}
return steps;
}
static long halving(int n) {
long steps = 0;
for (int i = n; i > 1; i /= 2) {
steps++;
}
return steps;
}
public static void main(String[] args) {
System.out.printf("%8s %10s %12s %8s%n",
"n", "O(n)", "O(n^2)", "O(log n)");
for (int n : new int[] {10, 100, 1_000, 10_000}) {
System.out.printf("%8d %10d %12d %8d%n",
n, linear(n), nested(n), halving(n));
}
}
} n O(n) O(n^2) O(log n)
10 10 100 3
100 100 10000 6
1000 1000 1000000 9
10000 10000 100000000 13Explanation:
linearruns its body once per element: 10 times more input means 10 times more steps. That is O(n).nestedruns the inner loopntimes for each of thenouter steps:n × n. Ten times more input means 100 times more steps. That is O(n²).halvingdividesiby 2 each time, so it needs only aboutlog₂ nsteps. Going from 1,000 to 10,000 adds just 4 steps. That is O(log n).
Rules for Working Out Big-O
| Rule | Example | Result |
|---|---|---|
| Drop constants | A loop that does 3 things per element: 3n | O(n) |
| Keep the biggest term | A loop, then a nested loop: n + n² | O(n²) |
| Sequential steps add | Two separate loops over the input: n + n | O(n) |
| Nested steps multiply | A loop over n containing a loop over m | O(n × m) |
| Halving gives log | Each step throws away half the remaining work | O(log n) |
| Watch hidden loops | list.contains(x) inside a loop | O(n²), not O(n) |
The last rule catches many people. A single line can hide a whole loop: list.contains, list.remove(0) on an ArrayList, String.indexOf, and stream().filter(...).count() all look at many elements. See "Time Complexity of Collection Operations" for the cost of every common collection method.
Space Complexity
Space complexity counts the extra memory an algorithm needs as n grows.
| Code | Extra memory | Space complexity |
|---|---|---|
A loop with a few int variables | A fixed handful of variables | O(1) |
| Copying the input into a new array | One new element per input element | O(n) |
A recursive method that goes n calls deep | One stack frame per call | O(n) |
Building an n × n table | n² cells | O(n²) |
Often you can trade one for the other: a HashSet uses O(n) extra memory to turn repeated O(n) lookups into O(1) ones.
Best, Average and Worst Case
| Case | Meaning | Linear search example |
|---|---|---|
| Best | The luckiest input | The item is first: 1 step |
| Average | A typical input | About n / 2 steps, still O(n) |
| Worst | The unluckiest input | The item is last or missing: n steps |
When someone says "this is O(n)" without saying which case, they almost always mean the worst case.
Interview Corner
What is the time complexity of HashMap.get?
O(1) on average. In the worst case, when many keys land in the same bucket, it is O(log n) in Java 8 and later, because crowded buckets turn into small balanced trees.
Is O(2n) different from O(n)?
No. Constants are dropped, so both are O(n). Big-O describes growth, not exact counts.
What is the complexity of this code: a loop over n that calls list.contains on a list of size n?
O(n²): contains is itself a loop of up to n steps, run n times.
Practice
- Easy: Add a method
cubic(int n)with three nested loops toGrowth.javaand add a column for it. How many steps does it take forn = 100? - Harder: Work out the Big-O of a method that loops
ifrom 1 ton, and inside it loopsjfrom 1 whilej < n, doublingjeach time. Then count the steps in code to check your answer.
Key Takeaways
Was this tutorial helpful?