Hello! Welcome to your first lesson. We're kicking off your journey to mastering LeetCode with the most fundamental concept in algorithm analysis: Big O notation.
In this lesson, you will learn how to analyze the time and space complexity of algorithms. This is the language used in technical interviews to discuss and compare the efficiency of different solutions. A solid grasp of Big O will allow you to quickly evaluate your own code and understand the trade-offs between different approaches, a skill that is absolutely essential for success in your upcoming internship interviews.
By the end of this 60-minute session, you will be able to identify and describe the efficiency of an algorithm in terms of its time and space requirements as its input grows.
1. What is Big O Notation?
At its core, Big O notation is a way to describe an algorithm's performance as the size of the input, which we'll call , gets very large. It doesn't measure the exact runtime in seconds, which can vary based on the computer's hardware. Instead, it measures the growth rate of the number of operations.
In interviews, we're typically concerned with two things:
- Time Complexity: How does the runtime of an algorithm grow as the input size increases?
- Space Complexity: How does the amount of extra memory the algorithm needs grow as the input size increases?
Crucially, Big O focuses on the worst-case scenario. This gives us an upper bound on performance, ensuring we know the maximum resources our algorithm might consume. Given your mathematics background, you can think of this as being similar to finding the dominant term in a polynomial to understand its end behavior as .
To get a quick, interview-focused overview of Big O, let's start with a video.
Big-O Notation - For Coding Interviews
This video from NeetCode provides a concise and practical introduction to Big O notation specifically for coding interviews. It explains why we ignore constants and introduces the most fundamental complexity class, O(1).
Watch the section from 00:44 to 02:53. Focus on understanding what Big O measures (runtime growth) and why we simplify expressions (e.g., ignoring constants like /2 or +5).
As the video explains, Big O simplifies complexity by focusing on the part of the expression that grows the fastest. An algorithm with operations and one with operations are both considered to have a linear growth rate, which we denote as .
2. Common Complexity Classes
Algorithms are categorized into different complexity classes based on their growth rates. Let's explore the most common ones you'll encounter, from most efficient to least efficient.
The following video provides an excellent tour of these classes with examples you'd see in coding problems.
Big-O Notation - For Coding Interviews
Continuing with the NeetCode video, this part walks through the most common complexity classes you will encounter, from linear O(N) to logarithmic O(log N) and exponential O(2^N).
Watch from 02:53 to 19:39. As you watch, try to connect each complexity class to a specific type of algorithm or operation (e.g., nested loops for O(N^2), dividing the problem in half for O(log N)).
To visualize the dramatic difference in these growth rates, take a look at this graph. Notice how quickly functions like and become computationally infeasible, even for small inputs.

Here is a handy "cheat sheet" that summarizes the common Big O notations with examples. This is a great reference to have.

3. How to Analyze Your Code
Now for the most important part: applying this knowledge to analyze code. The process can be broken down into a few simple rules.
Rules for Calculating Time Complexity
- Drop Constants: An algorithm that takes steps is simplified to . We care about the overall growth rate, not the exact multiplier.
- Drop Lower-Order Terms: An algorithm that takes steps is simplified to . For large , the term dominates and makes the term insignificant.
- Sequential Statements (Adding): If you have two loops that run one after the other, you add their complexities. For example, a loop that runs times followed by a loop that runs times is . If they both run times, it's .
- Nested Statements (Multiplying): If you have a loop inside another loop, you multiply their complexities. A loop that runs times with another -time loop inside it is .
This next resource provides a more detailed, step-by-step guide to analysis.
Time and Space Complexity: A Beginner's Guide
This guide from the Medium article 'Time and Space Complexity: A Beginner's Guide' provides a systematic way to analyze any piece of code.
Read the section titled 'Here’s a step-by-step guide to help you determine the time and space complexity of any code.' This section clearly explains how to handle loops, conditionals, recursion, and data structures.
A Note on Space Complexity
Space complexity analysis follows similar rules. We measure the additional space (or auxiliary space) used by the algorithm.
- Space: The algorithm uses a fixed amount of memory, regardless of input size (e.g., a few variables to store a sum or counters). This is also called an in-place algorithm.
- Space: The memory usage grows linearly with the input size. This often happens when you create a new data structure (like an array or hash set) that stores a copy of the input or some derivative of it.
- Space (Recursion): For recursive functions, space is consumed by the call stack. A function that recurses levels deep will typically use space.
Test your understanding!
What is the time and space complexity of the following Python function?
def find_sum_and_product(numbers):
sum_val = 0
product_val = 1
for num in numbers:
sum_val += num
for num in numbers:
product_val *= num
print(f"Sum: {sum_val}, Product: {product_val}")
Show answer
-
Time Complexity:
The function has two separateforloops. The first loop runs times (where is the length of thenumberslist). The second loop also runs times. Because the loops are sequential (one after the other), we add their complexities: . We then drop the constant 2, simplifying it to . -
Space Complexity:
The function uses a fixed number of variables (sum_val,product_val,num) to store data. The amount of memory used does not change regardless of the size of thenumberslist. Therefore, the space complexity is constant.
4. Putting It All Together with Examples
Let's look at a few more examples to solidify your understanding. The key is to break down the code into its constituent parts—loops, function calls, and memory allocations—and apply the rules we just discussed.
Test your understanding! (Nested Loops)
What is the time and space complexity of this Python function that prints all pairs from a list?
def print_all_pairs(items):
for item1 in items:
for item2 in items:
print(f"({item1}, {item2})")
Show answer
-
Time Complexity:
This function has a nested loop structure. The outer loop runs times, and for each of those iterations, the inner loop also runs times. Because the loops are nested, we multiply their complexities: . -
Space Complexity:
The function only uses a few variables to hold the current items (item1,item2). The memory required does not scale with the input size , so the space complexity is constant.
Test your understanding! (Recursion)
Consider the classic recursive solution for calculating the -th Fibonacci number. What are its time and space complexities?
def fibonacci(n):
if n <= 1:
return n
return fibonacci(n - 1) + fibonacci(n - 2)
Show answer
-
Time Complexity:
This is an example of exponential time complexity. Each call tofibonacci(n)generates two more calls (fibonacci(n-1)andfibonacci(n-2)), creating a binary tree of function calls. The number of calls grows exponentially with . This is very inefficient. (Later in the course, we'll see how to optimize this to using dynamic programming!). -
Space Complexity:
The space complexity is determined by the maximum depth of the recursion call stack. To calculatefibonacci(n), the function will recurse down tofibonacci(0), creating a chain of calls liken -> n-1 -> n-2 -> ... -> 0. The maximum depth of this chain is . Therefore, the space complexity is linear.
Conclusion
Great work today! You've covered the essential concepts of algorithmic complexity analysis, which will be your guide for the rest of this course.
Here are the key takeaways:
- Big O Notation describes the worst-case growth rate of an algorithm's time or space usage as the input size grows.
- We simplify Big O expressions by dropping constants and lower-order terms to focus on the dominant factor.
- Common complexity classes, in order of efficiency, include , , , , , and . Knowing which category your algorithm falls into is crucial.
- Analyzing code involves identifying sequential blocks (add complexities) and nested blocks (multiply complexities).
- Space complexity measures extra memory used, including the recursion call stack.
In our next lesson, we will dive into our first problem-solving pattern: array and string traversal. You'll immediately apply what you learned today to analyze the efficiency of algorithms that iterate through data collections.