Big-O in plain English with 4 common classes
Big-O describes how long an algorithm takes as input size grows, ignoring constants. **O(1)** is constant time: indexing a list, getting a dict value. **O(log n)** is logarithmic: binary search, balanced-tree lookup. **O(n)** is linear: scanning a list once. **O(n log n)** is the sorting band: Python's TimSort, mergesort, heapsort. **O(n squared)** is quadratic and shows up in nested loops over the same data, like bubble sort or naive duplicate detection.
The mental model: double the input and ask how the time changes. Constant time stays flat. Linear time doubles. Quadratic time quadruples. Logarithmic time adds one step. This is what graders mean when they reject a working solution with "your code is correct but too slow". The complexity, not the language, is the bottleneck.
Memory complexity follows the same notation. A recursive function that holds n frames on the call stack uses O(n) extra space. An in-place sort uses O(1) auxiliary memory plus the call stack. Course rubrics at MIT 6.100L and Stanford CS106A award separate marks for time and space analysis.
# Four loops, four complexity classes.
def constant_lookup(values):
# O(1): one index access, no loop over input
return values[0]
def linear_scan(values, target):
# O(n): one pass through the list
for x in values:
if x == target:
return True
return False
def quadratic_pairs(values):
# O(n^2): two nested loops over the same data
pairs = []
for i in values:
for j in values:
pairs.append((i, j))
return pairs
print(constant_lookup([10, 20, 30])) # 10
print(linear_scan([1, 2, 3, 4], 3)) # True
print(len(quadratic_pairs([1, 2, 3]))) # 9