Back to Course
Fundamentals

Big O Notation in Data Structures: Time and Space Complexity

Big O Notation is the mathematical notation used to describe the limiting behavior of a function when the argument tends toward infinity. In computer science, it is the standard tool for analyzing the time complexity and space complexity of algorithms. Big O describes the worst-case scenario, providing an upper bound on the growth rate of an algorithm.

What is Big O Notation?

Big O Notation expresses how the runtime or memory usage of an algorithm scales with the size of the input. It focuses on the dominant term and ignores constants and lower-order terms.

If f(n) = 3n² + 2n + 1, then f(n) = O(n²)

Because as n grows, the n² term dominates.

Why Big O Matters

  • Predicts Scalability: Tells us how an algorithm performs as input size increases.
  • Enables Comparison: Allows objective comparison between different algorithms.
  • Identifies Bottlenecks: Helps locate performance issues in code.
  • Industry Standard: Used in interviews, system design, and performance analysis.

Common Big O Complexity Classes

NotationNameExampleGrowth (n=10)Growth (n=100)
O(1)ConstantArray access11
O(log n)LogarithmicBinary search~3~7
O(n)LinearLinear search10100
O(n log n)LinearithmicMerge sort~33~664
O(n²)QuadraticBubble sort10010,000
O(n³)CubicMatrix multiplication1,0001,000,000
O(2ⁿ)ExponentialSubset sum (naive)1,0241.27e+30
O(n!)FactorialTSP (naive)3,628,8009.33e+157

Visualizing Growth

When n = 100:

  • O(1): 1 operation (blazing fast)
  • O(log n): ~7 operations
  • O(n): 100 operations
  • O(n log n): ~664 operations
  • O(n²): 10,000 operations
  • O(2ⁿ): ~1.27 × 10³⁰ operations (practically impossible)

Time Complexity Examples

O(1) — Constant Time

Independent of input size.

def constant_time(arr):
    return arr[0]  # Always one operation

O(log n) — Logarithmic Time

Each step reduces the input size by a factor.

def binary_search(arr, target):
    low, high = 0, len(arr) - 1
    while low <= high:
        mid = (low + high) // 2
        if arr[mid] == target:
            return mid
        elif arr[mid] < target:
            low = mid + 1
        else:
            high = mid - 1
    return -1

O(n) — Linear Time

Single loop over the input.

def linear_time(arr):
    total = 0
    for x in arr:
        total += x
    return total

O(n log n) — Linearithmic Time

Common in efficient sorting algorithms.

def merge_sort(arr):  # O(n log n)
    if len(arr) <= 1:
        return arr
    mid = len(arr) // 2
    left = merge_sort(arr[:mid])
    right = merge_sort(arr[mid:])
    return merge(left, right)

O(n²) — Quadratic Time

Nested loops over the input.

def quadratic_time(arr):
    for i in range(len(arr)):
        for j in range(len(arr)):
            print(arr[i], arr[j])

O(2ⁿ) — Exponential Time

Recursive solutions that branch exponentially.

def fibonacci(n):  # Naive recursive
    if n <= 1:
        return n
    return fibonacci(n - 1) + fibonacci(n - 2)
# Each call branches into two more calls

Space Complexity

Space complexity measures the amount of memory an algorithm uses relative to the input size. It includes:

  • Fixed part: Memory for constants, program code, and simple variables.
  • Variable part: Memory for data structures, recursion stack, and dynamically allocated memory.

Space Complexity Examples

def sum_array(arr):  # O(1) space
    total = 0
    for x in arr:
        total += x
    return total

def copy_array(arr):  # O(n) space
    return arr[:]  # Creates a new array of size n

def recursive_sum(arr):  # O(n) space (call stack)
    if not arr:
        return 0
    return arr[0] + recursive_sum(arr[1:])

Comparing Complexities

nO(1)O(log n)O(n)O(n log n)O(n²)O(2ⁿ)
1101012
101310331001,024
1001710066410,0001.27e+30
1,0001101,0009,9661,000,000~10³⁰¹
10,00011310,000132,877100,000,000~10³⁰¹⁰

How to Analyze Complexity

Step-by-Step Guide

  1. Identify the input size (n): Usually the number of elements.
  2. Count the operations: Loops, recursive calls, etc.
  3. Focus on the dominant term: Ignore constants and lower-order terms.
  4. Consider worst-case: Big O describes the upper bound.

Common Patterns

  • Single loop: O(n)
  • Nested loops: O(n²), O(n³), etc.
  • Divide and conquer: Often O(n log n) or O(log n)
  • Recursive branching: Often O(2ⁿ) or O(n!)
  • Hash operations: Usually O(1) average

Key Takeaways

  • Big O Notation is the standard for analyzing algorithm efficiency.
  • It describes the worst-case scenario and ignores constants and lower-order terms.
  • Time complexity measures runtime; space complexity measures memory usage.
  • Common classes: O(1), O(log n), O(n), O(n log n), O(n²), O(2ⁿ), O(n!).
  • Understanding Big O is essential for writing efficient, scalable code.

Ready to master Data Structures & Algorithms?

Learn DSA hands-on with mentor-led sessions, real interview practice, and placement support.

Explore Course