DEV Community

Shankar L
Shankar L

Posted on

Big-O Notation : Understanding Algorithm Efficiency

Why should you care?

You can write a program that works perfectly and still have a serious problem: it might be too slow when the input becomes large.

Consider two algorithms that search for an element.

Algorithm A takes:

10 operations for 10 elements
100 operations for 100 elements
1,000 operations for 1,000 elements
Enter fullscreen mode Exit fullscreen mode

Algorithm B takes:

3 operations for 10 elements
7 operations for 100 elements
10 operations for 1,000 elements
Enter fullscreen mode Exit fullscreen mode

Both might work perfectly for small inputs.

But as the input grows, their performance becomes dramatically different.

This is where Big-O notation becomes important.

Big-O helps us understand how the running time or memory requirements of an algorithm grow as the input size increases.

It is one of the most important concepts in computer science because it allows us to answer:

"How will this algorithm behave when my input becomes really large?"


The Problem

Suppose you have an array:

[10, 20, 30, 40, 50]
Enter fullscreen mode Exit fullscreen mode

and want to find:

40
Enter fullscreen mode Exit fullscreen mode

You could check every element:

10 → 20 → 30 → 40
Enter fullscreen mode Exit fullscreen mode

For five elements, this is trivial.

But imagine:

10,000 elements
Enter fullscreen mode Exit fullscreen mode

or:

1,000,000 elements
Enter fullscreen mode Exit fullscreen mode

or:

1,000,000,000 elements
Enter fullscreen mode Exit fullscreen mode

Now the algorithm's growth rate matters.

Another algorithm might be able to find the same element much faster.

For example, binary search can repeatedly divide the search space in half.

So we need a mathematical way to describe algorithmic growth.

That's what Big-O notation provides.


The Concept

Big-O notation describes the asymptotic growth of an algorithm.

In simple terms:

Big-O tells us how the amount of work grows as the input size grows.

We usually represent input size using:

n
Enter fullscreen mode Exit fullscreen mode

For example:

n = number of elements in an array
Enter fullscreen mode Exit fullscreen mode

If an algorithm examines every element:

n operations
Enter fullscreen mode Exit fullscreen mode

we write:

O(n)
Enter fullscreen mode Exit fullscreen mode

If it repeatedly divides the input in half:

O(log n)
Enter fullscreen mode Exit fullscreen mode

If it compares every element with every other element:

O(n²)
Enter fullscreen mode Exit fullscreen mode

Simple Explanation

Imagine you are looking for a person in a line.

O(1) — Constant

The person is always standing at a known position.

You immediately access them.

1 operation
Enter fullscreen mode Exit fullscreen mode

It doesn't matter whether there are:

10 people
Enter fullscreen mode Exit fullscreen mode

or:

1,000,000 people
Enter fullscreen mode Exit fullscreen mode

The work remains approximately constant.

Therefore:

O(1)
Enter fullscreen mode Exit fullscreen mode

O(n) — Linear

You check people one by one.

Person 1
Person 2
Person 3
...
Person n
Enter fullscreen mode Exit fullscreen mode

If the number of people doubles, the maximum amount of work approximately doubles.

Therefore:

O(n)
Enter fullscreen mode Exit fullscreen mode

O(log n) — Logarithmic

Imagine the people are sorted and you repeatedly eliminate half of them.

1,000,000
   ↓
500,000
   ↓
250,000
   ↓
125,000
   ↓
...
Enter fullscreen mode Exit fullscreen mode

You don't need to inspect everyone.

The search space shrinks exponentially.

Therefore:

O(log n)
Enter fullscreen mode Exit fullscreen mode

Binary search is the classic example.


O(n²) — Quadratic

Suppose every person needs to compare themselves with every other person.

For:

n people
Enter fullscreen mode Exit fullscreen mode

you can end up with approximately:

n × n
Enter fullscreen mode Exit fullscreen mode

comparisons.

Therefore:

O(n²)
Enter fullscreen mode Exit fullscreen mode

Nested loops are a common source of quadratic complexity.


Real-world Analogy

Imagine finding a book in a library.

O(n)

You start at the first book and check every book one by one.

Book 1
Book 2
Book 3
...
Book n
Enter fullscreen mode Exit fullscreen mode

O(log n)

The books are sorted alphabetically.

You open the middle of the library.

If your book should come before that point, ignore the second half.

Then repeat.

1,000,000 books
        ↓
500,000
        ↓
250,000
        ↓
125,000
        ↓
...
Enter fullscreen mode Exit fullscreen mode

This is logarithmic behavior.


O(1)

The library system tells you:

Computer Science → Shelf 42 → Position 17
Enter fullscreen mode Exit fullscreen mode

You directly access the location.

That's constant-time access.


Code Example

Let's look at some common examples.

O(1)

int getFirst(int[] arr) {
    return arr[0];
}
Enter fullscreen mode Exit fullscreen mode

Regardless of the array size:

10 elements
100 elements
1,000,000 elements
Enter fullscreen mode Exit fullscreen mode

we access one position.

Therefore:

O(1)
Enter fullscreen mode Exit fullscreen mode

O(n)

void printAll(int[] arr) {

    for (int i = 0; i < arr.length; i++) {
        System.out.println(arr[i]);
    }
}
Enter fullscreen mode Exit fullscreen mode

If there are n elements, the loop executes n times.

Therefore:

O(n)
Enter fullscreen mode Exit fullscreen mode

O(n²)

void printPairs(int[] arr) {

    for (int i = 0; i < arr.length; i++) {

        for (int j = 0; j < arr.length; j++) {
            System.out.println(arr[i] + ", " + arr[j]);
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

The outer loop executes:

n times
Enter fullscreen mode Exit fullscreen mode

The inner loop also executes:

n times
Enter fullscreen mode Exit fullscreen mode

Total:

n × n = n²
Enter fullscreen mode Exit fullscreen mode

Therefore:

O(n²)
Enter fullscreen mode Exit fullscreen mode

O(log n)

Binary search is a classic example:

int binarySearch(int[] arr, int target) {

    int left = 0;
    int right = arr.length - 1;

    while (left <= right) {

        int mid = left + (right - left) / 2;

        if (arr[mid] == target) {
            return mid;
        }

        if (arr[mid] < target) {
            left = mid + 1;
        } else {
            right = mid - 1;
        }
    }

    return -1;
}
Enter fullscreen mode Exit fullscreen mode

Every iteration eliminates approximately half of the remaining elements.

Therefore:

O(log n)
Enter fullscreen mode Exit fullscreen mode

Common Big-O Complexities

From generally more scalable to less scalable:

O(1)
   ↓
O(log n)
   ↓
O(n)
   ↓
O(n log n)
   ↓
O(n²)
   ↓
O(n³)
   ↓
O(2ⁿ)
   ↓
O(n!)
Enter fullscreen mode Exit fullscreen mode

A rough intuition:

Complexity Common Example
O(1) Array access
O(log n) Binary search
O(n) Linear search
O(n log n) Merge sort
O(n²) Nested loops
O(n³) Three nested loops
O(2ⁿ) Some recursive subset algorithms
O(n!) Brute-force permutations

The exact performance depends on the algorithm and implementation, but this hierarchy is extremely useful.


Dropping Constants

Consider:

void example(int[] arr) {

    for (int x : arr) {
        System.out.println(x);
    }

    for (int x : arr) {
        System.out.println(x);
    }
}
Enter fullscreen mode Exit fullscreen mode

The first loop takes:

n
Enter fullscreen mode Exit fullscreen mode

operations.

The second takes:

n
Enter fullscreen mode Exit fullscreen mode

operations.

Total:

2n
Enter fullscreen mode Exit fullscreen mode

Technically:

O(2n)
Enter fullscreen mode Exit fullscreen mode

But Big-O focuses on the growth rate, so we simplify:

O(n)
Enter fullscreen mode Exit fullscreen mode

We drop constant factors.

Similarly:

O(5n) → O(n)

O(100n) → O(n)
Enter fullscreen mode Exit fullscreen mode

Dropping Lower-Order Terms

Consider:

O(n² + n)
Enter fullscreen mode Exit fullscreen mode

As n becomes very large, grows much faster than n.

Therefore:

O(n² + n)
Enter fullscreen mode Exit fullscreen mode

becomes:

O(n²)
Enter fullscreen mode Exit fullscreen mode

Similarly:

O(n³ + n² + n)
Enter fullscreen mode Exit fullscreen mode

becomes:

O(n³)
Enter fullscreen mode Exit fullscreen mode

The dominant term determines the asymptotic growth.


Time Complexity vs Space Complexity

Big-O isn't only about execution time.

We can also analyze memory usage.

Time Complexity

How does the amount of computation grow?

Example:

for (int i = 0; i < n; i++) {
    System.out.println(i);
}
Enter fullscreen mode Exit fullscreen mode

Time:

O(n)
Enter fullscreen mode Exit fullscreen mode

Space Complexity

How much additional memory does the algorithm need?

Example:

int[] copy = new int[n];
Enter fullscreen mode Exit fullscreen mode

The new array grows with n.

Therefore:

O(n)
Enter fullscreen mode Exit fullscreen mode

space.

An algorithm can therefore have:

Time:  O(n)
Space: O(1)
Enter fullscreen mode Exit fullscreen mode

or:

Time:  O(n)
Space: O(n)
Enter fullscreen mode Exit fullscreen mode

These are separate measurements.


Common Mistakes

Mistake 1: Thinking Big-O gives the exact execution time

If an algorithm is:

O(n)
Enter fullscreen mode Exit fullscreen mode

that does not mean it takes exactly n milliseconds.

Big-O describes growth, not a stopwatch measurement.

Two O(n) algorithms can have very different real-world performance.


Mistake 2: Assuming O(1) means instantaneous

O(1) means the operation does not grow with input size.

It doesn't mean:

0 seconds
Enter fullscreen mode Exit fullscreen mode

or:

always extremely fast
Enter fullscreen mode Exit fullscreen mode

A constant-time operation could still perform a relatively expensive fixed amount of work.


Mistake 3: Counting every line equally

Consider:

for (int i = 0; i < n; i++) {
    System.out.println(i);
}
Enter fullscreen mode Exit fullscreen mode

The important question isn't:

"How many lines are in the code?"
Enter fullscreen mode Exit fullscreen mode

Instead ask:

"How many times does each operation execute as n grows?"
Enter fullscreen mode Exit fullscreen mode

The loop executes n times.

Therefore:

O(n)
Enter fullscreen mode Exit fullscreen mode

Mistake 4: Assuming nested loops always mean O(n²)

Consider:

for (int i = 0; i < n; i++) {

    for (int j = 0; j < 10; j++) {
        System.out.println(i + j);
    }
}
Enter fullscreen mode Exit fullscreen mode

The inner loop always runs only 10 times.

Total work:

10n
Enter fullscreen mode Exit fullscreen mode

Therefore:

O(n)
Enter fullscreen mode Exit fullscreen mode

not:

O(n²)
Enter fullscreen mode Exit fullscreen mode

Nested loops matter based on how their iteration counts depend on n.


Mistake 5: Ignoring input characteristics

Consider searching an array.

Linear search can be:

Best case:    O(1)
Worst case:   O(n)
Enter fullscreen mode Exit fullscreen mode

If the target is the first element, we're done immediately.

If it is the last element, we may inspect everything.

Big-O is often used for worst-case analysis, but best-case and average-case complexity can also be important.


Advanced Notes

1. Big-O vs Big-Theta vs Big-Omega

These notations are related but technically different.

Big-O:

O(f(n))
Enter fullscreen mode Exit fullscreen mode

represents an asymptotic upper bound.

Big-Omega:

Ω(f(n))
Enter fullscreen mode Exit fullscreen mode

represents an asymptotic lower bound.

Big-Theta:

Θ(f(n))
Enter fullscreen mode Exit fullscreen mode

represents a tight asymptotic bound.

For example, if an algorithm consistently grows linearly:

Θ(n)
Enter fullscreen mode Exit fullscreen mode

is a more precise statement than simply:

O(n)
Enter fullscreen mode Exit fullscreen mode

because O(n) technically allows functions that grow more slowly as well.

In everyday algorithm discussions, however, "Big-O" is commonly used to describe asymptotic complexity generally.


2. Amortized Complexity

Some operations are occasionally expensive but cheap on average over a sequence of operations.

A dynamic array is a good example.

Most insertions may take:

O(1)
Enter fullscreen mode Exit fullscreen mode

But occasionally the array becomes full and needs to resize.

That particular operation can take:

O(n)
Enter fullscreen mode Exit fullscreen mode

Yet insertion has an amortized O(1) complexity.

This is why analyzing a sequence of operations can sometimes be more useful than analyzing one operation in isolation.


3. Recursive Algorithms

Big-O becomes especially important with recursion.

Consider:

void countdown(int n) {

    if (n == 0) {
        return;
    }

    System.out.println(n);

    countdown(n - 1);
}
Enter fullscreen mode Exit fullscreen mode

There are n recursive calls.

Therefore:

Time:  O(n)
Space: O(n)
Enter fullscreen mode Exit fullscreen mode

The space complexity comes from the recursive call stack.


4. Exponential Complexity

Consider an algorithm that branches into two recursive calls:

void solve(int n) {

    if (n <= 0) {
        return;
    }

    solve(n - 1);
    solve(n - 1);
}
Enter fullscreen mode Exit fullscreen mode

The number of calls grows approximately exponentially.

Its complexity is roughly:

O(2ⁿ)
Enter fullscreen mode Exit fullscreen mode

This becomes impractical very quickly.

For example:

n = 10
→ around 1,000 operations

n = 20
→ around 1,000,000 operations

n = 30
→ around 1,000,000,000 operations
Enter fullscreen mode Exit fullscreen mode

This is why algorithm design matters so much.


5. Big-O and the Data Structures We Learned

Big-O connects directly to the data structures we've studied.

For example:

Data Structure Operation Typical Complexity
Array Access O(1)
Array Search O(n)
Linked List Access O(n)
Linked List Insert at head O(1)
Stack Push O(1)
Stack Pop O(1)
Queue Enqueue O(1)
Queue Dequeue O(1)
Hash Table Search O(1) average
BST Search O(log n) average
Heap Insert O(log n)
Heap Extract min/max O(log n)
Graph BFS/DFS O(V + E)
Trie Search O(L)

This is one reason Big-O should be learned alongside data structures.

The data structure you choose directly affects the complexity of the operations your program performs.


The Bigger Picture

Big-O gives us a way to compare the algorithms and data structures we have learned.

Imagine searching for an element.

With an unsorted array:

O(n)
Enter fullscreen mode Exit fullscreen mode

With a sorted array using binary search:

O(log n)
Enter fullscreen mode Exit fullscreen mode

With a hash table:

O(1) average
Enter fullscreen mode Exit fullscreen mode

With a balanced search tree:

O(log n)
Enter fullscreen mode Exit fullscreen mode

The problem may be the same:

"Find this element."
Enter fullscreen mode Exit fullscreen mode

But the data structure changes the algorithmic complexity.

This is one of the central ideas of computer science:

Choosing the right data structure can transform the performance of an algorithm.


The Most Important Mental Model

Don't memorize Big-O as a list of symbols.

Instead, ask:

"If I make the input 10× larger, how much more work does my algorithm have to do?"

For example:

O(1)
Enter fullscreen mode Exit fullscreen mode

Input grows → work stays roughly the same.

O(log n)
Enter fullscreen mode Exit fullscreen mode

Input grows → work increases slowly.

O(n)
Enter fullscreen mode Exit fullscreen mode

Input grows 10× → work grows roughly 10×.

O(n²)
Enter fullscreen mode Exit fullscreen mode

Input grows 10× → work grows roughly 100×.

O(2ⁿ)
Enter fullscreen mode Exit fullscreen mode

Input grows slightly → work can explode.

That mental model is more useful than simply memorizing the notation.


Summary

Big-O notation describes how an algorithm's resource requirements grow as the input size increases.

The most important complexities to recognize are:

O(1)       Constant
O(log n)   Logarithmic
O(n)       Linear
O(n log n) Linearithmic
O(n²)      Quadratic
O(2ⁿ)      Exponential
O(n!)      Factorial
Enter fullscreen mode Exit fullscreen mode

Remember:

  • Big-O describes growth, not exact execution time.
  • n usually represents input size.
  • Constants are ignored.
  • Lower-order terms are ignored.
  • Time and space complexity are separate.
  • Worst-case complexity is commonly discussed, but best and average cases also matter.
  • Data-structure choices strongly affect complexity.
  • A theoretically better complexity can become dramatically more important as n grows.

The ultimate goal isn't to memorize:

O(1), O(log n), O(n), O(n²)...
Enter fullscreen mode Exit fullscreen mode

The goal is to look at an algorithm and predict how it will scale.

Top comments (0)