AiTechWorlds
AiTechWorlds
University learning companion
Data Structures · Big O Notation: Measuring Code Efficiency
Use this course-level map to place “Big O Notation: Measuring Code Efficiency” in the larger system. It complements—not replaces—a topic-specific worked diagram.
Big O notation describes how an algorithm's running time or memory use grows as the size of its input grows, independent of hardware or language.
Imagine two chefs working in a restaurant kitchen. Both need to locate ingredients during a busy dinner service.
Chef A has a completely unorganized pantry. Flour could be anywhere. Every time a recipe calls for cumin, she scans every single shelf from left to right until she finds it.
Chef B organized the pantry before service: spices alphabetically on the top shelf, grains by weight on the middle shelf, oils on the bottom shelf. He walks straight to where an ingredient must be.
On a quiet Tuesday with 10 ingredients, both chefs manage fine. On a Saturday night with 500 ingredients, Chef A is a bottleneck. The restaurant grinds to a halt waiting for her to find the cardamom.
Big O notation is the tool for measuring this difference not in milliseconds, but in how the time grows as the pantry gets larger.
Big O notation describes the relationship between input size and resource usage (time or memory) as the input grows toward infinity.
Two critical rules:
Big O does not tell you how fast your code runs on your specific machine. It tells you how your code will behave as the problem gets bigger.
The letter n always represents the size of the input. A function that processes a list of 1,000 items has n = 1000.
| Big O | Name | Real World Analogy | 1,000 items → |
|---|---|---|---|
| O(1) | Constant | Dictionary lookup by key | ~1 operation |
| O(log n) | Logarithmic | Finding a word in a dictionary | ~10 operations |
| O(n) | Linear | Reading every page of a book | 1,000 operations |
| O(n log n) | Linearithmic | Sorting a deck of cards efficiently | ~10,000 operations |
| O(n²) | Quadratic | Comparing every pair in a group | 1,000,000 operations |
| O(2ⁿ) | Exponential | Trying every combination of a password | Effectively impractical |
Here is exactly how these complexities scale as n grows. These numbers make the differences visceral:
| n | O(1) | O(log n) | O(n) | O(n log n) | O(n²) | O(2ⁿ) |
|---|---|---|---|---|---|---|
| 10 | 1 | 3 | 10 | 33 | 100 | 1,024 |
| 100 | 1 | 7 | 100 | 664 | 10,000 | 10³⁰ |
| 1,000 | 1 | 10 | 1,000 | 9,966 | 1,000,000 | astronomical |
| 1,000,000 | 1 | 20 | 1,000,000 | 19,931,568 | 10¹² | impossible |
An O(n²) algorithm that takes 1 second for 1,000 items will take over 11 days for 1,000,000 items.
def get_first(arr):
return arr[0] # One operation, always
def dict_lookup(data, key):
return data[key] # Hash table: instant regardless of size
# Sample input
numbers = [42, 7, 15, 99, 3]
print(get_first(numbers)) # Output: 42
scores = {"Alice": 95, "Bob": 87, "Carol": 92}
print(dict_lookup(scores, "Carol")) # Output: 92Why O(1)? The operation does not depend on how large arr or data is. Whether there are 5 items or 5 million, the work is identical.
def binary_search(arr, target):
left, right = 0, len(arr) - 1
while left <= right:
mid = (left + right) // 2 # Check the middle element
if arr[mid] == target:
return mid
elif arr[mid] < target:
left = mid + 1 # Discard the left half
else:
right = mid - 1 # Discard the right half
return -1
sorted_list = [2, 5, 8, 12, 16, 23, 38, 56, 72, 91]
print(binary_search(sorted_list, 23)) # Output: 5 (index)
print(binary_search(sorted_list, 99)) # Output: -1 (not found)Why O(log n)? Each step cuts the remaining search space in half. For 1,024 items, it takes at most 10 comparisons (2¹⁰ = 1,024). For 1,048,576 items, just 20 comparisons.
def find_max(arr):
max_val = arr[0]
for item in arr: # Visits every element once
if item > max_val:
max_val = item
return max_val
def linear_search(arr, target):
for i, item in enumerate(arr): # Worst case: check all n items
if item == target:
return i
return -1
numbers = [34, 7, 23, 32, 5, 62]
print(find_max(numbers)) # Output: 62
print(linear_search(numbers, 23)) # Output: 2Why O(n)? The loop runs once per element. Double the input size, double the work. Simple and direct.
def has_duplicate(arr):
for i in range(len(arr)):
for j in range(i + 1, len(arr)): # Nested loop: n × n comparisons
if arr[i] == arr[j]:
return True
return False
def bubble_sort(arr):
n = len(arr)
for i in range(n):
for j in range(0, n - i - 1): # Also O(n²)
if arr[j] > arr[j + 1]:
arr[j], arr[j + 1] = arr[j + 1], arr[j]
return arr
items = [3, 1, 4, 1, 5, 9, 2, 6]
print(has_duplicate(items)) # Output: True
print(bubble_sort([64, 34, 25, 12, 22])) # Output: [12, 22, 25, 34, 64]Why O(n²)? For every one of the n elements in the outer loop, the inner loop runs up to n times. Total operations: approximately n × n = n².
def two_loops(arr):
for x in arr: print(x) # O(n)
for x in arr: print(x) # O(n)
# Total: O(2n) → simplified to O(n)We write O(n), not O(2n). Constants do not affect the shape of growth.
def mixed(arr):
for x in arr: # O(n)
for y in arr: print(x, y) # O(n²)
# Total: O(n² + n) → simplified to O(n²)As n grows, n² overwhelms n completely. The lower term disappears.
def search(arr, target):
for item in arr: # Best case: O(1) if target is first
if item == target: # Worst case: O(n) if target is last
return True
return FalseBig O always refers to the worst case unless explicitly stated otherwise.
Big O applies to memory usage, not just time.
# O(1) space — uses same memory regardless of input size
def sum_list(arr):
total = 0
for x in arr:
total += x
return total
# O(n) space — creates a new list as large as the input
def double_all(arr):
return [x * 2 for x in arr] # New list of n elements
# O(n) space — call stack grows with n during recursion
def countdown(n):
if n == 0: return
countdown(n - 1) # Each call adds a frame to the stackA function can have excellent time complexity but poor space complexity always consider both.
Developers sometimes say, "My computer is fast, so O(n²) is fine." This is one of the most dangerous assumptions in software engineering.
A modern laptop might execute 100 million simple operations per second. Here is what that means at scale:
| Algorithm | n = 10,000 | n = 100,000 |
|---|---|---|
| O(n) | 0.0001 seconds | 0.001 seconds |
| O(n log n) | 0.0013 seconds | 0.017 seconds |
| O(n²) | 10 seconds | 1,666 minutes |
A 10x increase in data turned a 10-second process into a 28-hour process. Hardware cannot save bad complexity choices.
In the next lesson, you will apply this framework immediately as you study arrays the most fundamental data structure in all of computing.
University learning companion
Data Structures · Big O Notation: Measuring Code Efficiency
Complete the steps without looking at the answer first; record evidence rather than only a final result.
These are primary or established learning references for this subject. Check the relevant chapter or specification for the exact topic and version.
Get this course's notes on Telegram!
Free cheat sheets, summaries & practice exercises