Hello! Welcome to the third lesson in our module on "Analyzing Algorithm Efficiency."
In our last session, we got our hands dirty by meticulously counting the primitive operations in algorithms. We ended up with precise but complex expressions for an algorithm's work, like 4n - 1 for finding a maximum value, or (n² - n) / 2 for checking for duplicates.
You might have felt that this process was a bit tedious. While it's a crucial skill to understand where the work comes from, in practice, we rarely use these exact formulas. The differences between 4n - 1 and 4n + 5 are insignificant when n is a million. What truly matters is the part of the expression that grows the fastest—the 4n or the n².
Today, we will learn the standard, powerful notation used by programmers and computer scientists to formalize this idea. Your learning outcome is to define Big O notation and use it to express an algorithm's time complexity. This is the language of algorithm analysis and a fundamental concept for technical interviews.
1. The Big Picture: What is Big O?
At its heart, Big O notation is a way to describe how an algorithm's runtime scales as the size of the input grows. It's not about measuring time in seconds; it's about understanding the relationship between the input size and the number of operations.
This first article explains the concept from a practical, interview-oriented perspective.
Big O Notation Time and Space Complexity
The article 'Big O Notation Time and Space Complexity' from Skilled.dev provides an excellent, non-intimidating introduction to why Big O is important for evaluating code.
Please read the first three sections: the introduction, 'How Candidates are Graded for Hiring', and 'What Big O Means'. Focus on the idea that Big O provides an objective way to measure an algorithm's scalability, which is critical when dealing with very large inputs.
The key idea is that we want to understand an algorithm's performance when the input size, n, is very large. As the article highlights, the difference between an efficient and an inefficient algorithm becomes dramatic as n increases.
This is best understood visually.

As you can see, an algorithm with O(n²) complexity becomes unusably slow for large inputs compared to an O(n) algorithm. Big O notation gives us the tool to make these comparisons.
2. The Rules of Simplification
So, how do we get from our exact operation count like 4n - 1 to a clean Big O expression? We follow a few simple rules, which are all aimed at isolating the term that has the biggest impact on growth as n becomes large.
This video from CS Dojo provides a great explanation of the simplification process.
Introduction to Big O Notation and Time Complexity (Data Structures & Algorithms #7)
In this section of his 'Introduction to Big O Notation' video, Y.K. from CS Dojo explains how to simplify complexity expressions to arrive at the final Big O notation.
Watch the clip from 22:12 to 25:22. The video clearly explains the two main rules: dropping constants and dropping non-dominant (lower-order) terms.
Let's formalize these rules, which are also outlined in the "4 Steps to Calculate Big O" section of the Skilled.dev article you read earlier (LINK).
Rule 1: Keep the Fastest-Growing Term (Drop Non-Dominant Terms)
An algorithm's runtime is often a sum of operations, like n² + n + 50. As n gets very large, the n² term will grow so much faster than the n term that the n term's contribution becomes negligible.
- Example: If
n = 1,000,000, thenn²is one trillion, whilenis "only" one million. Then²term completely dominates. - Application: For an operation count of
T(n) = n² + n + 50, we drop thenand50, leaving us withn².
Rule 2: Drop Constant Factors
Big O notation ignores constant multipliers. The reason is that these constants depend more on the programming language or hardware than on the algorithm's fundamental design. An algorithm with 2n operations and one with 10n operations both scale linearly with the input size. If you double the input, the runtime roughly doubles for both.
- Example: We treat
T(n) = 4nandT(n) = 100nas being in the same complexity class. - Application: For a count of
T(n) = 4n, we drop the constant4, leaving us withn.
Applying the Rules to Our Previous Examples
Let's revisit the operation counts from our last lesson.
-
find_maxfunction: We calculated a worst-case count ofT(n) = 4n - 1.- Apply Rule 1: The fastest-growing term is
4n. We drop the-1. We are left with4n. - Apply Rule 2: We drop the constant
4. - Result: The time complexity is
O(n)(read as "Big O of n" or "Order n").
- Apply Rule 1: The fastest-growing term is
-
has_duplicatesfunction: We calculated a worst-case count ofT(n) = (n² - n) / 2, which is0.5n² - 0.5n.- Apply Rule 1: The fastest-growing term is
0.5n². We drop-0.5n. We are left with0.5n². - Apply Rule 2: We drop the constant
0.5. - Result: The time complexity is
O(n²)(read as "Big O of n-squared" or "Order n-squared").
- Apply Rule 1: The fastest-growing term is
You can now see how Big O gives us a much cleaner and more direct way to express the core scalability of an algorithm.
3. The Formal Definition of Big O
Given your math background, it's useful to see the formal definition behind this. Big O is a statement about an upper bound.
The following video provides a concise mathematical definition.
1.8.1 Asymptotic Notations Big Oh - Omega - Theta #1
Professor Abdul Bari gives a formal, mathematical definition of Big O notation. This adds a layer of precision to our understanding.
Watch from 02:49 to 03:25. This short segment presents the core definition.
Formally, we say that a function T(n) is O(g(n)) if there exist positive constants c and n₀ such that:
Let's break this down:
T(n)is our exact operation count (e.g.,4n - 1).g(n)is the function we use in the Big O notation (e.g.,n).- The definition says that
T(n)is "in the order of"g(n)if, after some input sizen₀, our functionT(n)will always be less than or equal tog(n)multiplied by some constantc.
In essence, c * g(n) provides an upper bound on our function's growth.
For our T(n) = 4n - 1 example, we can show it is O(n).
Let g(n) = n. Can we find constants c and n₀ such that 4n - 1 \le c \cdot n for all n \ge n₀?
Yes. If we choose c = 4, the inequality becomes 4n - 1 \le 4n, which is true for all n \ge 1. So, we can pick c=4 and n₀=1.
A key point, mentioned in Abdul Bari's video (around timestamp 08:58), is that while 4n - 1 is also technically O(n²) (we could pick c=1 and n₀=4), we always strive to find the tightest possible upper bound. The tightest bound for 4n - 1 is O(n).
4. Practice: Combining Complexities
Let's analyze a function that has multiple parts with different complexities.
def process_data(data):
n = len(data)
# Part 1: A simple loop
print("Printing all items...")
for item in data:
print(item)
# Part 2: A nested loop
print("Comparing all pairs...")
for i in range(n):
for j in range(n):
print(f"Comparing item {i} with item {j}")
How would we determine the Big O complexity of the entire process_data function?
- Analyze Part 1: The first
forloop iterates through the list once. This is a classic linear operation. Its complexity isO(n). - Analyze Part 2: The nested
forloops executen * n = n²times. Its complexity isO(n²). - Combine and Simplify: The total work is the sum of the parts:
O(n) + O(n²). Now, we apply our simplification rules. Then²term is the fastest-growing (dominant) term. - Final Complexity: We drop the lower-order
O(n)term. The overall time complexity ofprocess_dataisO(n²).
Conclusion
Congratulations! You've just learned the single most important concept for analyzing algorithm efficiency.
Key Takeaways:
- Big O notation describes the growth rate of an algorithm's runtime as the input size (
n) becomes very large. - It provides a high-level, standard way to compare the scalability of different algorithms.
- To find the Big O complexity, we simplify the exact operation count by:
- Focusing on the worst-case scenario.
- Dropping constant factors.
- Keeping only the fastest-growing (dominant) term.
O(n)describes a linear growth rate, whileO(n²)describes a quadratic growth rate, which is significantly slower for large inputs.
Preview of the Next Lesson:
Now that you understand what Big O is and the rules for deriving it, the next step is to get faster at recognizing it. In the next lesson, you will learn to identify the time complexity of common patterns: O(1), O(log n), O(n), and O(n²). This will allow you to look at a piece of code and almost instantly classify its efficiency, a critical skill for both problem-solving and technical interviews.