Hello! Welcome to your second lesson.
In our first session, we established the "what" and "why" of Big O notation—it's the language we use to describe an algorithm's scalability. You learned how to simplify complexity expressions and got a high-level tour of the common complexity classes.
Today, we move from theory to practice. This lesson is all about applying that knowledge to the most common structures in any program: loops. Your goal is to calculate the time complexity of simple loops and nested loops. This skill is the first practical step in analyzing any algorithm you'll encounter in an interview or write in your daily work.
The Basic Principle of Loop Analysis
Analyzing a loop's complexity boils down to a simple formula:
Total Time = (Number of Iterations) × (Complexity of Operations Inside the Loop)
For the loops we'll study today, we'll assume the operations inside the loop are "basic"—things like arithmetic, assignments, or simple comparisons. These are constant time, or O(1), operations. Therefore, our main job is to accurately count the number of iterations the loop performs in the worst-case scenario.
To get a formal overview of this process, please take a look at the following reading.
Time Complexity Analysis of Loop in Programming
This article from 'enjoy-algorithm' on Medium provides a clear, step-by-step method for analyzing loop complexity.
Read the introductory section and the 'Steps to analyze the time complexity of the loop'. This formalizes the approach we will be taking.
1. Single Loops: Linear Time Complexity — O(n)
The most straightforward case is a single loop that iterates over a collection of size n.
Consider this simple loop written in Rust:
fn print_items(n: i32) {
for i in 0..n { // This loop runs 'n' times
// This is an O(1) operation
println!("Item: {}", i);
}
}
Here's the analysis:
- Number of Iterations: The loop runs from
0up to (but not including)n. This gives usniterations. - Complexity Inside: The
println!macro is a single operation. We consider itO(1). - Total Time:
niterations ×O(1)per iteration =O(n).
This is called linear time complexity. If the input n doubles, the number of operations roughly doubles.
What about loops that look slightly different? For example, a loop that decrements, or one that skips items? The following video provides a concise analysis of these common variations.
Dr. Abdul Bari provides a clear, mathematical breakdown of how to analyze various simple loops.
Watch the segment from 00:00 to 02:13. Notice how loops that count down (i--) or increment by a constant (i += c) are all still O(n) because we drop constant factors like 1/c.
The key insight is that whether a loop runs n times, n/2 times, or n/20 times, its growth rate is still directly proportional to n. In Big O notation, they all simplify to O(n).
2. Nested Loops: Quadratic Time Complexity — O(n²)
Things get more interesting when we place loops inside other loops. This is a very common pattern, especially when dealing with 2D structures like grids or when comparing every element in a collection to every other element.
Case 1: Independent Nested Loops
This is the classic O(n²) case where the inner loop's iterations do not depend on the outer loop's variable.
fn print_pairs(n: i32) {
for i in 0..n { // Outer loop runs 'n' times
for j in 0..n { // Inner loop runs 'n' times for EACH outer iteration
// O(1) operation
println!("Pair: ({}, {})", i, j);
}
}
}
Analysis:
- The outer loop runs
ntimes. - For each of those
niterations, the inner loop runsntimes. - Total Time:
n(outer) ×n(inner) ×O(1)(body) =O(n²).
This is called quadratic time complexity. If n doubles, the runtime quadruples (2²=4). This can become slow very quickly as n grows.

The NeetCode video you saw in the last lesson has an excellent segment explaining this for 2D arrays, which is a perfect real-world example.
Big-O Notation - For Coding Interviews
Let's revisit the NeetCode video to see a practical explanation of O(n²) using the example of iterating over a grid.
Watch the segment from 06:37 to 08:00. This section clearly shows how iterating over an n x n grid leads to n * n operations.
Case 2: Dependent Nested Loops
A slightly more complex, but equally common, pattern is when the inner loop's number of iterations depends on the outer loop's current variable.
fn print_lower_triangle(n: i32) {
for i in 0..n { // Outer loop runs 'n' times
for j in 0..i { // Inner loop's iterations depend on 'i'
// O(1) operation
println!("Pair: ({}, {})", i, j);
}
}
}
How do we analyze this? We can't simply multiply n * n. Let's trace the number of inner loop iterations for each value of i:
- When
i = 0, the inner loop runs 0 times. - When
i = 1, the inner loop runs 1 time (j=0). - When
i = 2, the inner loop runs 2 times (j=0, 1). - ...
- When
i = n-1, the inner loop runsn-1times.
The total number of operations is the sum of an arithmetic series: 0 + 1 + 2 + ... + (n-1).
This sum has a well-known formula: (n-1) * n / 2.
Expanding this gives (n² - n) / 2 = 0.5n² - 0.5n.
Applying our Big O simplification rules from the last lesson:
- Drop lower-order terms:
O(0.5n² - 0.5n)becomesO(0.5n²). - Drop constants:
O(0.5n²)becomesO(n²).
Even though it does roughly half the work of the independent nested loop, it has the same quadratic growth rate. Dr. Bari's video provides a fantastic, detailed trace of exactly this scenario.
This is a crucial pattern to understand. Watch how Dr. Bari traces the execution step-by-step to arrive at the O(n²) complexity.
Watch from 02:38 to 05:55. Pay close attention to the table he builds to count the total operations. This demonstrates mathematically why the complexity is O(n²).
3. Consecutive Loops: Additive Complexity — O(n + m)
What if loops are not nested, but run one after the other?
fn two_separate_loops(n: i32, m: i32) {
for i in 0..n {
// O(1) operation
println!("First loop: {}", i);
}
for j in 0..m {
// O(1) operation
println!("Second loop: {}", j);
}
}
In this case, we simply add the complexities:
- The first loop is
O(n). - The second loop is
O(m). - Total Time:
O(n) + O(m) = O(n + m).
If both loops run n times, the complexity is O(n) + O(n) = O(2n), which simplifies to O(n). When complexities are added, the final Big O is determined by the dominant (largest) term. For example, a nested loop followed by a single loop (O(n²) + O(n)) simplifies to O(n²).
Key Takeaways
- Single Loop: A loop that iterates
ntimes hasO(n)complexity, assuming its body isO(1). - Independent Nested Loops: An outer loop running
ntimes and an inner loop runningmtimes results inO(n * m)complexity. If both runntimes, it'sO(n²). - Dependent Nested Loops: A common pattern where the inner loop runs
itimes (whereiis the outer loop index) also results inO(n²), due to the sum of an arithmetic series. - Consecutive Loops: The complexities are added, e.g.,
O(n) + O(m) = O(n + m). The final Big O is determined by the highest-order term.
Next Steps
You can now look at basic loop structures and determine their time complexity. This is the bedrock of algorithm analysis.
In the next lesson, we will shift our focus from time to memory. You will learn to calculate the space complexity of algorithms, which measures how much auxiliary memory an algorithm uses as its input size grows.