Lesson 2 / 6

Big O Notation: Measuring Efficiency

Big O notation describes how an algorithm’s running time (or memory use) grows as the input size grows — not the exact number of seconds, which depends on the specific computer, but the underlying pattern of growth.

Common complexities, from best to worst

O(1)        constant     -- looking up a value by key in a dictionarynO(log n)    logarithmic  -- binary search in a sorted listnO(n)        linear       -- checking every item in a list oncenO(n log n)  linearithmic -- efficient sorting algorithms (merge sort, quicksort)nO(n²)       quadratic    -- comparing every item to every other item (nested loops)

n represents the size of the input. An O(n) algorithm run on a list twice as long takes roughly twice as long; an O(n²) algorithm run on a list twice as long takes roughly four times as long — the gap between these grows dramatically as n grows.

A concrete example

def has_duplicate_slow(items):        # O(n²) -- nested loop compares every pairn    for i in range(len(items)):n        for j in range(len(items)):n            if i != j and items[i] == items[j]:n                return Truen    return Falsenndef has_duplicate_fast(items):        # O(n) -- one pass, using a set for O(1) lookupsn    seen = set()n    for item in items:n        if item in seen:n            return Truen        seen.add(item)n    return False

Both functions answer the exact same question correctly. On a list of 100,000 items, the difference between these two is the difference between a program that responds instantly and one that visibly hangs.

Space complexity

Big O also describes memory use, not just time — has_duplicate_fast above trades a small amount of extra memory (the seen set) for a large improvement in speed, a trade-off that shows up constantly throughout this entire course.