Welcome to the first lesson of your Data Structures & Algorithms course! Your goal is to excel in technical interviews for top engineering roles, and mastering DSA is the most critical step. Today, we begin with the absolute foundation upon which all algorithm analysis is built: Time Complexity and Big-O Notation.
This lesson will teach you how to measure the efficiency of your code. By the end, you will be able to look at a piece of iterative or recursive code and determine how its runtime scales with the size of the input. This is the single most common question you will be asked after writing code in an interview, so let's build a strong foundation.
What is Time Complexity?
First, let's dispel a common myth. Time complexity is not about measuring how long an algorithm takes to run in seconds or milliseconds. That time can change dramatically depending on the computer's speed, the programming language, and other factors.
Instead, time complexity is a way to describe how the number of operations an algorithm performs grows as the input size (n) increases. It measures the rate of growth or the scalability of an algorithm. An efficient algorithm is one that performs well even when the input size becomes very large.
To understand this core concept, I recommend watching the beginning of this video from the Strivers A2Z DSA Course series. It gives a great explanation of why we focus on scalability rather than raw execution time.
Time and Space Complexity - Strivers A2Z DSA Course
Watch this first section of the video "Time and Space Complexity" from the take U forward channel.
The key part to watch is from the beginning up to the introduction of Big-O notation. Pay close attention to the graph and the analogy of running code on an old Windows machine versus a new Macbook, which illustrates why time complexity is about the rate of growth, not just the time taken.
The Language of Efficiency: Big-O Notation
To formally describe time complexity, we use Big-O notation. It gives us a standardized way to talk about the "worst-case scenario" for an algorithm's runtime. For example, if we say an algorithm is (pronounced "Big O of n"), we mean that in the worst case, the number of operations grows linearly with the input size, .
To calculate Big-O, we don't count every single operation. Instead, we follow three simplifying rules. These rules are fundamental to everything we will do in this course.
The Three Rules of Big-O:
- Analyze the Worst-Case Scenario: We always prepare for the worst. If an algorithm could be fast on some inputs but very slow on others, we care about its performance on the slow ones. This gives us a guaranteed performance boundary.
- Drop Constants: For large inputs, constant factors don't significantly impact the growth rate. An algorithm that takes steps and one that takes steps are both considered to have the same complexity class, , because they both grow linearly.
- Drop Non-Dominant Terms: We only care about the term that grows the fastest. In an expression like , the term will dwarf the others as gets large. So, the complexity is simply .
The Striver video you just started explains these rules very clearly. Please continue watching it to see detailed examples for each rule.
Time and Space Complexity - Strivers A2Z DSA Course
Continue with the video "Time and Space Complexity".
Watch the sections that introduce Big-O and explain the three core rules. Focus on: The introduction to Big-O notation. The detailed explanation of the three rules: worst-case, dropping constants, and dropping lower values. The summary of Big-O (upper bound), Omega (lower bound), and Theta (average bound) at the end. In interviews, Big-O is what matters most. For a text-based alternative, you can read the first part of the associated article.
Time and Space Complexity - Strivers A2Z DSA Course - Tutorial
This article covers the same fundamental concepts as the video.
Read the sections that define time complexity and explain the rules for calculating it. Focus on the definition of time complexity, and then the three rules: the three rules. The article provides mathematical examples that illustrate why we drop constants and lower-order terms.
Analyzing Iterative Algorithms (Loops)
Now let's apply these rules to actual Java code. The most common patterns you'll see involve loops.
1. Constant Time:
An algorithm is if it takes the same number of operations regardless of the input size.
// Given an array of numbers, return the first element.
public int getFirstElement(int[] numbers) {
return numbers[0]; // This is one operation, regardless of the array's size.
}
2. Linear Time:
The running time grows in direct proportion to the input size, . This is typical for algorithms that iterate through a collection once.
// Print all elements in an array.
public void printAllElements(int[] numbers) {
for (int i = 0; i < numbers.length; i++) { // The loop runs 'n' times, where n is numbers.length.
System.out.println(numbers[i]);
}
}
3. Quadratic Time:
The running time is proportional to the square of the input size. This often occurs with nested loops where both loops iterate up to .
// Print all pairs of elements in an array.
public void printAllPairs(int[] numbers) {
for (int i = 0; i < numbers.length; i++) { // Outer loop runs 'n' times.
for (int j = 0; j < numbers.length; j++) { // Inner loop runs 'n' times for each outer iteration.
System.out.println(numbers[i] + ", " + numbers[j]);
}
}
} // Total operations are roughly n * n = n^2.
4. Logarithmic Time:
The running time grows logarithmically with the input size. This is common in algorithms that divide the problem in half with each step, like binary search.
// A loop where the counter is doubled in each iteration.
public void logarithmicExample(int n) {
for (int i = 1; i < n; i = i * 2) { // i goes 1, 2, 4, 8, 16, ... until it reaches n.
System.out.println("Step " + i);
}
}
// If n=32, the loop runs 5 times (2^5 = 32). If n=1024, it runs 10 times (2^10 = 1024).
// The number of operations is log base 2 of n.
The following resource from GeeksforGeeks provides a nice catalog of these common loop structures. It will help you quickly recognize complexity patterns.
How to Analyse Loops for Complexity Analysis of Algorithms
This article provides clear, concise examples for different complexity classes in loops.
Focus on the Java code snippets and explanations for: Constant Time O(1) Linear Time O(n) Quadratic Time O(n^2) Logarithmic Time O(log n) Consecutive loops, which shows how complexities are added.
Analyzing Recursive Algorithms
Analyzing recursion is slightly different because we can't just count loops. Instead, we define the time taken by a function call in terms of the calls it makes to itself. This is called a recurrence relation.
Let's analyze the classic recursive factorial function:factorial(n) = n * factorial(n-1)
Let's denote the time complexity of factorial(n) as .
- Inside the function, we perform one multiplication and one subtraction, which are constant time operations, let's call it or simply
c. - The function then calls
factorial(n-1), which takes time. - So, the recurrence relation is: **
** - The base case,
factorial(0), takes constant time: **
**
We can solve this by substitution:
- ...after k steps...
To reach the base case , we set .
Using our Big-O rules, we drop the constants and non-dominant terms, which gives us .
This method of setting up and solving a recurrence relation is a key skill. The following video provides a very clear, animated explanation of this process for the factorial function.
Time and space complexity analysis of recursive programs - using factorial
Watch this segment from the video "Time and space complexity analysis of recursive programs" by mycodeschool.
This section focuses exclusively on analyzing the time complexity. Watch the part that derives the recurrence relation for factorial and solves it to find the Big-O complexity. The visual step-by-step reduction is extremely helpful for understanding the method.
The Big Picture: Visualizing Growth
To wrap your head around why this matters so much, look at the following chart. It shows how drastically the number of operations explodes for less efficient complexities as the input size (n) grows.

This is why an interview question isn't just about getting a correct answer; it's about finding a solution that lives in the green or yellow zones.
Key Takeaways
- Time Complexity measures how the runtime of an algorithm scales with the input size, not the exact time in seconds.
- Big-O Notation is the standard for expressing the worst-case time complexity.
- The Three Rules: Always analyze the worst case, and when simplifying, drop constants and drop non-dominant terms.
- Iterative Complexity: You can often determine complexity by analyzing loop structures:
- No loops (or loops with a fixed number of iterations):
- A single loop up to :
- A nested loop up to :
- A loop that halves the problem size at each step:
- Recursive Complexity: This is analyzed using a recurrence relation, which expresses the time of a problem in terms of its subproblems.
Next Steps
Well done on completing your first lesson! You've just learned the language that we'll use to evaluate every algorithm and data structure from now on.
In our next lesson, we will explore the other side of the coin: Space Complexity. You'll learn how to analyze the memory usage of an algorithm, including the often-hidden memory consumed by the recursion stack.