Big-O notation scares a lot of people because it often gets explained as math first and problem solving second.
In interviews, that is backward.
You do not need to sound like a theoretician. You need to look at code, estimate how work grows as input grows, compare two approaches, and explain your trade-offs clearly. That is what interviewers are really checking.
This article teaches Big-O from a Java engineer's point of view.
What Big-O Actually Measures
Big-O describes how an algorithm grows when the input size grows.
Usually we care about:
- time complexity: how the number of operations grows
- space complexity: how much extra memory grows
The key word is grows.
Big-O does not care about whether a loop runs in 7 milliseconds or 10 milliseconds on your laptop. It cares about the shape of growth:
- constant
- linear
- logarithmic
- quadratic
- and so on
That is why these two implementations are both O(n) even if one is faster in practice:
int sum(int[] nums) {
int total = 0;
for (int num : nums) {
total += num;
}
return total;
}
int doubledSum(int[] nums) {
int total = 0;
for (int num : nums) {
total += num * 2;
}
return total;
}
The second loop does a little more work per element, but both still scale linearly with n.
Why Big-O Matters in Interviews
Interview problems are often designed so that:
- a brute-force solution is easy to see
- a better solution exists if you spot the pattern
The better solution usually improves complexity.
Example:
- checking every pair in an array:
O(n^2) - using a hash map:
O(n) - using binary search on sorted data:
O(log n)per lookup
If you can say, "This brute-force approach is O(n^2), but we can reduce it to O(n) using extra space," you immediately sound more structured.
The Most Common Complexity Classes
Here is the mental cheat sheet worth memorizing:
| Complexity | Meaning | Example |
|---|---|---|
O(1) |
does not grow with input size | array access by index |
O(log n) |
work shrinks by half each step | binary search |
O(n) |
one pass over input | linear scan |
O(n log n) |
split + merge / efficient sort | merge sort, heap sort |
O(n^2) |
nested full scans | compare every pair |
O(2^n) |
explore all subsets | brute-force recursion |
O(n!) |
explore all permutations | permutation generation |
In interview settings, the most common practical jump is:
O(n^2) -> O(n log n) or O(n)
That usually comes from sorting, hashing, or a better traversal pattern.
How to Analyze Time Complexity Step by Step
A calm way to analyze code:
- identify the input size
- count how many times each major block runs
- keep the dominant term
- drop constants
Example:
boolean containsDuplicate(int[] nums) {
Set<Integer> seen = new HashSet<>();
for (int num : nums) {
if (seen.contains(num)) {
return true;
}
seen.add(num);
}
return false;
}
Analysis:
- loop runs
ntimes HashSet.contains()is averageO(1)HashSet.add()is averageO(1)
So the total time is:
n * O(1) = O(n)
Extra memory:
- the set may store all elements
So space complexity is:
O(n)
Ignore Constants, Keep the Growth
Suppose you have:
for (int i = 0; i < n; i++) {
// O(1)
}
for (int i = 0; i < n; i++) {
// O(1)
}
That is:
O(n) + O(n) = O(2n) = O(n)
Now compare it with nested loops:
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
// O(1)
}
}
That becomes:
O(n * n) = O(n^2)
The important question is not "how many loops do I see?"
It is:
- are they sequential?
- or is one inside another?
Time Complexity Patterns You Will See Again and Again
1. Single Loop -> Usually O(n)
int max(int[] nums) {
int answer = nums[0];
for (int i = 1; i < nums.length; i++) {
answer = Math.max(answer, nums[i]);
}
return answer;
}
One pass over the array means O(n).
2. Nested Full Loops -> Usually O(n^2)
boolean hasPairWithSum(int[] nums, int target) {
for (int i = 0; i < nums.length; i++) {
for (int j = i + 1; j < nums.length; j++) {
if (nums[i] + nums[j] == target) {
return true;
}
}
}
return false;
}
In the worst case, you compare almost every pair.
That is O(n^2).
3. Halving the Search Space -> O(log n)
int binarySearch(int[] nums, int target) {
int left = 0;
int right = nums.length - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] == target) {
return mid;
}
if (nums[mid] < target) {
left = mid + 1;
} else {
right = mid - 1;
}
}
return -1;
}
Each step removes half of the remaining range, so the number of iterations is O(log n).
4. Sort First, Then Scan -> Often O(n log n)
boolean containsDuplicate(int[] nums) {
Arrays.sort(nums);
for (int i = 1; i < nums.length; i++) {
if (nums[i] == nums[i - 1]) {
return true;
}
}
return false;
}
Sorting dominates:
Arrays.sort(nums)for primitives isO(n log n)- scan is
O(n)
Overall:
O(n log n) + O(n) = O(n log n)
Space Complexity: Do Not Forget It
Candidates often say time complexity and stop there.
You will stand out if you also mention memory.
Compare these two solutions for duplicate detection:
boolean containsDuplicateWithSet(int[] nums) {
Set<Integer> seen = new HashSet<>();
for (int num : nums) {
if (!seen.add(num)) {
return true;
}
}
return false;
}
boolean containsDuplicateBySorting(int[] nums) {
Arrays.sort(nums);
for (int i = 1; i < nums.length; i++) {
if (nums[i] == nums[i - 1]) {
return true;
}
}
return false;
}
Typical interview summary:
- hash set solution:
O(n)time,O(n)extra space - sorting solution:
O(n log n)time, oftenO(1)orO(log n)extra space depending on implementation details
That kind of comparison shows maturity.
Java Collections Complexity Cheat Sheet
For interviews, you should know the common average-case costs:
| Java structure | Common operations | Complexity |
|---|---|---|
ArrayList |
get(index) |
O(1) |
ArrayList |
append at end | amortized O(1) |
ArrayList |
insert in middle | O(n) |
LinkedList |
add/remove at ends | O(1) |
LinkedList |
random access | O(n) |
HashMap |
get, put, containsKey |
average O(1) |
HashSet |
add, contains, remove |
average O(1) |
TreeMap |
get, put, remove |
O(log n) |
TreeSet |
add, contains, remove |
O(log n) |
PriorityQueue |
offer, poll |
O(log n) |
ArrayDeque |
add/remove at ends | O(1) |
Two interview notes matter here:
HashMapandHashSetare average-caseO(1), not guaranteed worst-caseO(1)ArrayListappend is amortizedO(1), not strictO(1)for every single operation
What Amortized Complexity Means
This confuses many people at first, so let us make it concrete.
When an ArrayList runs out of capacity, Java allocates a bigger array and copies elements over. That resize step is expensive.
But it does not happen on every append.
So if you append n items:
- most appends are cheap
- a few resizes are expensive
- averaged across many operations, append is amortized
O(1)
That is why this is still considered efficient:
List<Integer> values = new ArrayList<>();
for (int i = 0; i < n; i++) {
values.add(i);
}
Even though some individual add() calls trigger a copy, the whole loop is still O(n).
How to Analyze Recursion
For recursion, ask two questions:
- how many recursive calls are made?
- how much work happens in each call?
Simple example:
int factorial(int n) {
if (n <= 1) {
return 1;
}
return n * factorial(n - 1);
}
There are n calls, and each one does constant work outside the recursive call.
So:
- time complexity:
O(n) - recursion stack space:
O(n)
Now compare with Fibonacci brute force:
int fib(int n) {
if (n <= 1) {
return n;
}
return fib(n - 1) + fib(n - 2);
}
This branches twice and repeats subproblems heavily.
That is exponential time: O(2^n).
This is exactly why dynamic programming matters.
A Dry Run: From Brute Force to Better
Take classic Two Sum.
Brute force:
int[] twoSum(int[] nums, int target) {
for (int i = 0; i < nums.length; i++) {
for (int j = i + 1; j < nums.length; j++) {
if (nums[i] + nums[j] == target) {
return new int[] {i, j};
}
}
}
return new int[] {-1, -1};
}
Complexity:
- time:
O(n^2) - space:
O(1)
Optimized with HashMap:
int[] twoSum(int[] nums, int target) {
Map<Integer, Integer> indexByValue = new HashMap<>();
for (int i = 0; i < nums.length; i++) {
int needed = target - nums[i];
if (indexByValue.containsKey(needed)) {
return new int[] {indexByValue.get(needed), i};
}
indexByValue.put(nums[i], i);
}
return new int[] {-1, -1};
}
Complexity:
- time:
O(n) - space:
O(n)
This is how interview discussions usually go:
- write the brute force
- explain the bottleneck
- replace repeated work with a better data structure
- state the new complexity
Common Big-O Mistakes in Interviews
Mistake 1: Counting library calls as magic
Do not say Collections.sort() is "just one line" so it must be cheap.
Library calls still have complexity.
Mistake 2: Ignoring hidden data structure cost
This is not O(1):
list.add(0, value);
For ArrayList, inserting at the front shifts the rest of the array, so it is O(n).
Mistake 3: Confusing average and worst case
HashMap is average O(1), but not every scenario is constant time.
In interviews, "average O(1)" is the right phrase.
Mistake 4: Forgetting recursion stack space
Even if you do not allocate an array or map, recursive calls consume memory.
Mistake 5: Reporting only the final answer
It is much better to say:
The brute-force version is O(n^2) time and O(1) space.
We can reduce it to O(n) time by using a HashMap, which costs O(n) space.
That shows decision-making, not memorization.
Practical Interview Script
When you finish a solution, say something like:
We scan the array once, so time complexity is O(n).
The HashMap stores up to n elements, so space complexity is O(n).
This improves the brute-force O(n^2) approach by avoiding repeated pair checks.
Short, clear, and complete.
Big-O Practice Questions
Practice analyzing these without writing full code first:
- Find the maximum element in an array
- Check whether a string is a palindrome
- Find duplicates in an array
- Merge two sorted arrays
- Find the first occurrence of a target in a sorted array
- Generate all subsets of a set
- Reverse a linked list
- Traverse a binary tree level by level
For each one, ask:
- what is the brute-force complexity?
- is the input sorted?
- can hashing help?
- can two pointers help?
- can we trade space for time?
That habit is what makes Big-O feel useful instead of abstract.
Final Takeaways
- Big-O is about growth, not exact runtime
- identify the input size before analyzing
- nested loops often mean
O(n^2) - halving often means
O(log n) - sorting often makes the solution
O(n log n) - always mention both time and space
- in interviews, compare brute force with the improved approach
If you get good at complexity analysis, a lot of DSA stops feeling random. You start recognizing the same trade-offs in different clothes.
