Welcome to the first lesson of your journey to becoming a Codeforces Expert! To consistently solve problems beyond the basics, the most critical skill isn't just knowing many algorithms, but knowing which algorithm is fast enough for a given problem. This is where the concepts of time and space complexity come in.
In this lesson, we will lay the essential groundwork for your entire competitive programming career. We'll demystify Big O notation, which is the language we use to talk about algorithm efficiency. You'll learn how to look at a piece of code—a simple loop, nested loops, or even a recursive function—and determine its time and space complexity. This will enable you to start thinking like an expert: analyzing the problem constraints first to understand what kind of solution the judges are looking for, even before you write a single line of code.
What is Time Complexity?
First, let's establish what time complexity is not. It's not about measuring how many seconds your code takes to run on your laptop. That time can change dramatically based on your computer's speed, the programming language, and other factors.
Instead, time complexity describes the relationship between the size of the input to a problem (which we'll call n) and the number of operations the algorithm performs. It measures how the runtime scales as the input n gets larger.
To get a clear intuition for this, let's watch a short segment from the "Strivers A2Z DSA Course".
Time and Space Complexity - Strivers A2Z DSA Course
This video provides a great explanation of why "time taken" is not a reliable measure and introduces time complexity as the "rate of increase" of time with respect to input size.
Please watch from the beginning up to the introduction of the graph example. Focus on the distinction between "time taken" and the "rate at which time taken increases". Watch from the introduction.
As the video explains, we need a standardized way to describe this rate of growth. This is where Big O notation comes in.
The Language of Efficiency: Big O Notation
Big O notation is the mathematical language used in computer science to describe the performance or complexity of an algorithm. When we analyze an algorithm, we're usually interested in its worst-case performance, as this gives us an upper bound on how long it might take.
To make the analysis simpler and more general, Big O notation follows three key principles:
- Always analyze the worst-case scenario: We want a guarantee that our algorithm will be fast enough, even for the trickiest test cases.
- Ignore constants: An algorithm that takes
3noperations and one that takesnoperations are both considered to have a linear, orO(n), complexity. The constant factor (3) becomes insignificant asngrows very large. - Ignore lower-order terms: If an algorithm takes operations, we only care about the fastest-growing term, . For large
n, the part dominates so completely that thenpart is negligible. So, we simplify this to .
The same video explains these rules with clear examples.
Time and Space Complexity - Strivers A2Z DSA Course
This next segment explains the three rules for calculating Big O notation: focusing on the worst case, and avoiding constants and lower-order values.
Watch the section that introduces the three rules and provides examples for each one. Pay attention to how a complex expression like 4n^3 + 3n^2 + 8 simplifies to just O(n^3). Watch from the three rules.
To solidify this, let's look at a textual explanation with some more examples. The following reading from a Codeforces blog post gives a concise definition of Big O.
This section provides a formal yet easy-to-understand definition of Big O notation.
Please read the section titled "Big O Notation". Focus on how it states we ignore constants and only consider the highest power of the input size n.
Analyzing Code Complexity
Now for the practical part: how do you look at a piece of C++ code and figure out its Big O complexity? The most common way is to analyze the loops.
-
A single
forloop that runsntimes performsniterations. If the work inside the loop takes constant time, the total complexity is .for (int i = 0; i < n; i++) { // O(1) constant time work } -
Two separate, sequential loops have their complexities added. A loop that runs
Ntimes followed by a loop that runsMtimes results in complexity.for (int i = 0; i < N; i++) { /* O(1) work */ } for (int j = 0; j < M; j++) { /* O(1) work */ } -
Nested loops have their complexities multiplied. A loop that runs
ntimes with another loop inside it that also runsntimes results in .for (int i = 0; i < n; i++) { for (int j = 0; j < n; j++) { // O(1) constant time work } }
Let's dive into some more detailed examples from the Codeforces blog.
This "Theoretical Analysis" section breaks down how to count operations in different loop structures, which is the core skill for complexity analysis.
Read the entire "Theoretical Analysis" section. Pay close attention to the examples: The simple for loop. The nested loop example that results in O(n^2). Notice the analysis: the inner loop runs n times, then n-1, then n-2, etc., which sums to approximately \frac{n(n-1)}{2}, which simplifies to O(n^2). The tricky nested loop that is actually O(n). This is a great example of why you can't just count the number of for keywords. You must analyze how the loop counters change.
A Hierarchy of Common Complexities
Not all complexities are created equal. An algorithm will be much slower than an one for large inputs. It's crucial to have a mental picture of how these complexities compare.

Here's a list of common complexities, from fastest (excellent) to slowest (horrible):
- - Constant: The runtime doesn't depend on the input size. (e.g., accessing an array element at a known index).
- - Logarithmic: The runtime grows very slowly. If
ndoubles, the runtime increases by a small constant amount. (e.g., Binary Search). - - Linear: The runtime grows proportionally to the input size. (e.g., finding the maximum element in an unsorted array).
- - Log-linear: A very common and efficient complexity for sorting algorithms. (
std::sortin C++ is typically ). - - Quadratic: Runtime grows with the square of the input size. Often involves nested loops over the input. (e.g., Bubble Sort, simple nested
forloops). - - Exponential: Becomes extremely slow even for small
n. Often seen in problems that involve generating all subsets of a set. - - Factorial: The slowest of all. Usually associated with generating all permutations of a set.
You already know about binary search and sorting. Let's see how their complexities are formally derived.
This part of the article explains the logic behind two very important complexity classes you're already familiar with: O(log n) and O(n log n).
First, read the analysis of Binary Search. The key idea is that the problem size is halved at each step. Then, skim the Recurrence Relations section to see the analysis for Merge Sort, which gives us our canonical O(n \log n) complexity. You don't need to master recurrence relations right now, but it's good to see where this common complexity comes from.
What About Space Complexity?
Besides time, we also care about the memory an algorithm uses. Space complexity measures the amount of memory an algorithm needs, as a function of the input size n. We usually focus on auxiliary space complexity, which is the extra space used by the algorithm beyond the space required to store the input itself.
- An algorithm that only uses a few variables, regardless of the input size, has space complexity.
- An algorithm that creates a new array or vector of the same size as the input has space complexity.
- A recursive function that calls itself
ntimes before returning might use space on the call stack.
This "Big-O Cheat Sheet" is an invaluable resource that summarizes the time and space complexity for many common data structures and algorithms. It's a great reference to bookmark.

The Most Important Trick for Competitive Programmers
Here is the key takeaway that will immediately elevate your problem-solving strategy. On most competitive programming platforms like Codeforces, a modern server can execute approximately 10⁸ (100 million) operations in one second.
This single fact allows you to work backward from the problem constraints to figure out what time complexity is required.
Let's watch a short video that explains this powerful "reverse trick".
LeetCode Feels Easy After This Reverse Runtime Trick
This video brilliantly frames the idea of using constraints to determine the required complexity as a "trick" used by experienced programmers.
The video explains how online judges run your code and why constraints matter. First, watch the introduction to understand the core idea: using constraints. Then, skip to the part that gives the crucial number of operations: maximum operations. They estimate 10-20 million, but 100 million (10⁸) is a safe upper bound to use for Codeforces. Finally, watch the breakdown of what complexity is needed for different input sizes (N): the N-rules.
Here's a quick summary of those N-rules for a 1-second time limit:
- If : An or solution might pass.
- If : An solution is likely fine. ()
- If : An solution should pass. ()
- If : You need an or solution. is definitely too slow.
- If : You likely need an or solution. Even will be too slow.
This is arguably the most important skill in competitive programming strategy. Before you even think about an algorithm, look at the constraints for N. They are a massive hint from the problem setter about the expected solution complexity.
Conclusion
In this lesson, you've taken the first and most fundamental step toward thinking like an expert competitive programmer.
Here are the key takeaways:
- Time complexity describes how an algorithm's runtime scales with input size, not its absolute speed in seconds.
- Big O notation is the standard for expressing this, focusing on the worst-case and ignoring constants and lower-order terms.
- You can analyze code, particularly loops, to determine its Big O complexity (, , etc.).
- Space complexity measures the auxiliary memory used by an algorithm.
- The crucial insight for competitive programming is that you can use the problem's input constraints and the "10⁸ operations per second" rule to deduce the required time complexity before you start coding.
In the next lesson, we will put this last point into practice. We'll look at several Codeforces problems and focus solely on their constraints to predict the required time complexity, a skill that will guide your choice of algorithm and save you countless hours of debugging TLE solutions.