Overview
- This is part of an article series on Algorithm Complexity Analysis.
- In Part I, we cover key topics related to Big O notation.
Table of Contents
- What is Big O?
- What does Asymptotic and Asymptotic Notation mean?
- Time Complexity
- Space Complexity
- Determining the Complexity of a Recursive Algorithm
- Big-O Complexity Chart
- Four rules of Big O
- Why do we consider exchanging space complexity in favor of time complexity worth it?
- Pros of Big O
- Cons of Big O
- References
What does Asymptotic and Asymptotic Notation mean?
-
Problems with measuring time and space by just running the algorithm:
- Results vary across different computers.
- Background processes and daemons can affect performance even on the same computer.
- Without the computing power of companies like Google, we rely on asymptotic notation to understand algorithm efficiency with large inputs.
-
That’s why we use Asymptotic notation; to consistently evaluate time and space complexity, machine-independently.
-
Asymptotic:- While having a background in calculus is helpful, it's not strictly necessary to understand asymptotic analysis.
- Asymptotic analysis studies how a function behaves as its input approaches infinity. For example, consider a function
f(x); as x increases more and more, how does the function behave? Watch this
-
Asymptotic notation:- Like calculus uses limits to analyze behavior at extremes, asymptotic notation uses:
-
Big Ofor the upper limit (worst-case). -
Big Omegafor the lower limit (best-case). -
Big Thetafor the tight bound (typically used for average-case or general characterization).
- We normally refer to Algorithmic Complexity with expressions like:
factorial time,quadratic time,linear time, and so on.- This means we are not describing a specific graph, but rather classifying the growth behavior of the algorithm's runtime.
What is Big O?
- Big O is a notation used to describe an algorithm's time complexity and space complexity in terms of the input size.
- How the execution time (or memory usage) of an algorithm increases as the size of the input grows.
Time Complexity
-
What is it?
- The amount of time an algorithm takes to run as a function of the input size.
- It gives an estimate of the efficiency of an algorithm and helps us understand how it will scale as the input grows.
-
How to measure it?
- Count the number of basic operations (like comparisons or loops) an algorithm performs relative to the input size.
1. Constant Time - O(1)
- No matter the input size, the algorithm performs a fixed number of operations.
function getFirstElement(arr) {
return arr[0]; // Only one operation, regardless of array size
}
2. Linear Time - O(n)
- The algorithm performs operations proportional to the input size.
function printAllElements(arr) {
for (let i = 0; i < arr.length; i++) {
console.log(arr[i]); // Loops through all elements
}
}
3. Quadratic Time - O(n^2)
- The algorithm performs operations proportional to the square of the input size, often seen in nested loops.
function quadraticTime(arr) {
for (let i = 0; i < arr.length; i++) {
for (let j = 0; j < arr.length; j++) {
console.log(arr[i], arr[j]); // Loops inside loops
}
}
}
Space Complexity
-
What is it?
- Space complexity measures the amount of memory an algorithm uses relative to the input size.
- It helps evaluate the efficiency of memory usage as the input size increases.
-
How to measure it?
- Count the amount of memory required for variables, data structures, and recursive calls as the input size grows.
- A common mistake when analyzing algorithms is confusing
Auxiliary SpacewithSpace Complexity; but they’re not the same:-
Auxiliary Space: Extra memory used by the algorithm, excluding the input/output. -
Space Complexity: Total memory used, including input, output, and auxiliary space.
-
- Below we will have some examples of
Auxiliary Space.
1. Constant Space - O(1)
- No matter the size of the input, the algorithm uses a constant amount of auxiliary space.
function constantSpace(array) {
let sum = 0; // One variable sum
for (let i = 0; i < array.length; i++) {
// One variable i
sum += array[i];
}
return sum;
}
2. Linear Space - O(n)
- The algorithm spends proportional auxiliary space to the input size.
function linearSpace(array) {
let result = []; // new array size n
for (let i = 0; i < array.length; i++) {
result.push(array[i] * 2); // storing at this array size n
}
return result;
}
3. Quadratic Space - O(n^2)
- The algorithm spends proportional auxiliary space to the square of the input size.
function quadraticSpace(n) {
let matrix = []; // new 2d array
for (let i = 0; i < n; i++) {
matrix[i] = []; // new row for each item (rows size n)
for (let j = 0; j < n; j++) {
matrix[i][j] = i + j; // populate the columns of each row (columns size n)
}
}
return matrix;
}
Determining the Complexity of a Recursive Algorithm
- In this section, we will see how to determine space and time complexity for recursive algorithms, but we will not dive deeply into Recurrence Relations and the Master Theorem.
- We’ll use the classic example: recursive Fibonacci.
function recursiveFib(n) {
if (n == 0 || n == 1) return n;
return recursiveFib(n - 1) + recursiveFib(n - 2);
}
- Time Complexity: O(2^n)
- Each call branches into two others, forming a binary tree with depth
n. - This exponential growth leads to ~2^n calls.
- It's not exactly 2^n, but we drop constants and consider O(2^n).
-
Space Complexity: O(n)
- This comes from how the call stack works during recursion.
- When a function calls itself, it’s paused and added to a stack until the recursive call finishes.
- In the worst case (e.g., leftmost path of the call tree), the stack can grow to depth
nbefore the functions finish and return. - So, the maximum number of active function calls at once is proportional to
n.
-
For better understanding, look:
Big-O Complexity Chart
Four rules of Big O
1. Worst Case
- If we got Jack as the first user, we would have O(1), but this case doesn't matter for Big O notation.
- Big O cares only about the worst case, and this is O(n).
function findJack(users) {
for (let i = 0; i < users.length; i++) {
const user = users[i];
if (user.name === "Jack") {
console.log(user);
break;
}
}
}
2. Drop Constants
- What matters is the asymptotic behavior, which means that we analyze the growth of an algorithm as the input size
napproaches infinity. And because of that, constants become negligible compared to how the function grows.- O(2n), O(100n) and O(10000n) all grow linearly as
ngrows. - The constant factors don't affect the shape of the growth curve, just the initial height.
- O(2n), O(100n) and O(10000n) all grow linearly as
- It's also good because it simplifies comparisons, for example, it's easy to see that
O(n log n)is better thanO(n^2).
// - This is not O(2n), because Big O measures growth patterns, not exact performance.
// - So this is O(n).
function minMax(array) {
let min = null,
max = null;
array.forEach((e) => {
min = MIN(e, min);
});
array.forEach((e) => {
max = MAX(e, max);
});
}
3. Different Inputs => Different Variables
- This is not O(n^2) because it's not the same input.
- It is O(a * b).
function intersection(arrayA, arrayB) {
let count = 0;
arrayA.forEach((a) => {
arrayB.forEach((b) => {
if (a === b) {
count += 1;
}
});
});
}
4. Drop Non-Dominant Terms
- We do this for the same reason we drop constants: to focus on the worst-case growth rate.
- Only the fastest-growing term affects Big O.
// - This is not O(n + n^2), because we drop the non-dominant term.
// - So it's O(n^2).
function minMax(array) {
// O(n)
let min = null;
let max = null;
array.forEach((e) => {
min = MIN(e, min);
});
console.log(min);
// O(n^2)
array.forEach((e1) => {
array.forEach((e2) => {
console.log(e1, e2);
});
});
}
Why do we consider exchanging space complexity in favor of time complexity worth it?
- Space complexity = how much memory (RAM, disk, etc.) a program uses
- Time complexity = how long the program takes to run
- This trade-off is often considered a good deal because, in many cases, we can buy more memory, but we can't buy more time.
- For example, in the easy Leetcode problem "Two Sum", we can trade Auxiliary Space from O(1) to O(n) in order to improve time complexity from O(n^2) to O(n) by using a hash table.
Pros of Big O
-
1 - Useful for comparing Algorithms thanks to Asymptotic Analysis
- It provides a high-level view of the algorithm efficiency which makes easy to see that O(n) is much faster than O(n^2) for larger inputs.
-
2 - Helps us to understand the trade-off between space and time.
- With practice, we notice that in many cases, as mentioned before, we can make a trade-off between space and time, depending on which one we need more.
-
3 - It's theoretical and can be generalized
- If we tried to consider each hardware, OS, and other factors to measure our code's efficiency, it would never be precise, as it can vary depending on many "external" conditions.
- It's similar to how physicists make assumptions 'in a vacuum' to focus on fundamental principles, ignoring external influences like air resistance that would complicate the model.
Cons of Big O
- The problem is the misuse of Big O notation; not Big O itself.
These issues arise not from Big O itself, but from relying on it exclusively without considering other complexity analysis notations and average or best case scenarios.
-
1 - Focuses on worst case
- In some scenarios, using Big O is appropriate; for example, in a time-critical system running on an airplane flying over a mountain. If the algorithm is only designed for average or best-case scenarios rather than the worst case, the consequences could be serious.
- However, it's important to recognize that in some cases, analyzing the average or best case may be more useful, depending on the specific use case of the algorithm.
-
2 - Ignores constants
- Only shows asymptotic growth; O(1000n) and O(n) considered same thing.
- This is good in general but can mislead in real-world scenario with many constant factors.
- Read for more details: When do Constants Matter?
- If you will work with small inputs it's important to care about constants.
References
- https://web.stanford.edu/class/archive/cs/cs106b/cs106b.1176/handouts/midterm/5-BigO.pdf
- https://pages.cs.wisc.edu/~vernon/cs367/notes/3.COMPLEXITY.html
- https://www.bigocheatsheet.com/
- https://dev.to/coderjay06/four-rules-for-big-o-1915
- https://www.geeksforgeeks.org/analysis-algorithms-big-o-analysis/
- https://www.youtube.com/watch?v=myZKhztFhzE&ab_channel=BackToBackSWE
- https://www.geeksforgeeks.org/g-fact-86/
- https://jarednielsen.com/big-o-recursive-time-complexity/
- https://jarednielsen.com/big-o-recursive-space-complexity/
- https://skilled.dev/course/big-o-time-and-space-complexity
Thanks for Reading!
- Feel free to reach out if you have any questions, feedback, or suggestions. Your engagement is appreciated!
- Repository DSA Studies
Contacts
- You can find this and more content on:


Top comments (0)