Skip to main content

Command Palette

Search for a command to run...

Stop Writing Slow Code: Understanding Big O in C++

Published
9 min read
Stop Writing Slow Code: Understanding Big O in C++

Have you ever written code that works perfectly on your local machine with 10 items, but completely freezes when you test it with 10,000 items?

I have. And it’s usually not because the logic is broken - it’s because the logic isn't scalable. And today we’ll be talking about that.


What Actually is Big O?

Forget the complex math definitions for a second.

Big O Notation is simply the language we use to talk about how long an algorithm takes to run as the input size grows.

We don't measure speed in seconds, because different computers have different processors. Instead, we measure speed in steps.

  1. If you have 10 inputs, how many steps does your code take?
  1. If you have 1,000,000 inputs, how many steps does it take?

It describes the Worst Case Scenario. If you are searching for a name in a list, the "Best Case" is that it's the first name you look at. The "Worst Case" (Big O) is that it's the very last name.


The Big O Time Complexities -

Let's look at the four most common complexities you will face, ranked from Fast to Slow.

1.O(1) - Constant Time (Very Fast)

O(1) means the code takes the exact same amount of time, no matter how much data you have.

Analogy: Opening a book to page 50. It takes one motion. It doesn't matter if the book has 100 pages or 10,000 pages; opening it to a specific page always takes the same effort.

int getItem(vector<int>& arr, int index){
    return arr[index]; 
}
//this always takes 1 step regardless of size.

2. O(log n) - Logarithmic Time (Divider)

This is incredibly efficient. It usually appears in algorithms that divide the problem in half every step, like Binary Search.

Analogy: Looking up a word in a physical dictionary. You don't read every page. You open the middle, check if your word is before or after, and ignore the half you don't need. You keep cutting the book in half until you find it.

int binarySearch(vector<int>& arr, int target) {
    int left = 0, right = arr.size() - 1;
    while (left <= right){
        int mid = left + (right - left)/2;
        if(arr[mid] == target){
             return mid;
         }
        if(arr[mid] < target)
           left = mid + 1;
        else right = mid - 1;
    }
    return -1;
}

3. O(n) - Linear Time(The Reader)

This is standard performance. If you double your input, the time it takes doubles.

Analogy: Reading a book page by page. If a book is 100 pages, it takes 2 hours. If it is 200 pages, it takes 4 hours.

void printAll(vector<int>& arr){
    for (int val : arr){
        cout<<val<<" ";
    }
}

4. O(n²) - Quadratic Time (Danger Zone)

This is where most “it suddenly froze” stories are born. Every time you add a nested loop without thinking, you’re probably writing O(n²).

Analogy: You have 100 friends and you want to introduce everyone to everyone else at a party (handshakes problem).

  • 10 friends = 45 handshakes

  • 100 friends = ~5000 handshakes

  • 10,000 friends = almost 50 million handshakes = your app dies.

for(int i = 0; i < n; i++){
    for(int j = 0; j < n; j++){
        if(arr[i] + arr[j] == target){  
            return {i, j};
        }
    }
}

5. O(n logn) - Fast Enough For Everything

This is the complexity of most built-in sort functions - sort() in Python/C++. You’ll almost never need anything faster than this for real-world problems.

Analogy: Organizing a huge pile of books by height using divide and conquer in merge sort style.

sort(arr.begin(), arr.end());

6. The One Complexity That Humiliates : O(n.k) – The “Fake Linear” Trap

You look at the code. One loop. You pat yourself on the back: “Nice, O(n).” You ship it.

Congratulations - you just got murdered by O(n⋅k), the most deceptive complexity in all of programming.

Why It Feels Like O(n) But Isn’t

  1. n = number of items (usually huge)

  2. k = size of each item (strings, arrays, digits, buckets, etc.)

  3. You only see the outer loop over n → brain says “linear”

  4. Inside that loop you do work that grows with k → total = n × k

When k is small (≤ 20), nobody notices. When k becomes 1000, 10 000, or 100 000… your server melts.

Hall of Fame Killers (You’ve Definitely Written at Least One) -

  1. String Multiplication -
for i in range(len(num1)):          
    for j in range(len(num2)):
  1. Naive anagram grouping (again, but worse) -
for(const word of words){                 
    for(const other of words){            
        if(sorted(word) === sorted(other)) 
    }
}
  1. LeetCode’s Longest Palindromic Substring brute force Expand around center is O(n⋅k) because each expansion can take up to k steps.

Bottom line: O(n ⋅ k) is the reason smart people write code that passes all tests but dies in production.

Moral: Never, ever, trust a single loop that does non-constant work per element. It’s not O(n). It’s O(n⋅k).


Space Complexity - Which Everyone Forgets

Why Space Complexity Matters More in 2025 Than Ever

In 2015 you could allocate a few hundred MB and nobody cared. In 2025:

  1. Mobile browsers kill tabs at ~100 - 300 MB heap.

  2. Serverless functions (Vercel, AWS Lambda) have 1 - 10 GB limits but cold starts hate big memory.

  3. Low-end Android devices in India/Brazil still ship with 2 - 4 GB RAM.

  4. Docker containers in Kubernetes get OOM-killed without warning.

One unnecessary array can take your app from “snappy” to “crashes on 10 % of users”.

int create2DArray(n){
    let arr = [];
    for(let i = 0; i < n; i++){
        arr[i] = [];               
        for(let j = 0; j < n; j++){
            arr[i][j] = 0;
        }
    }
    return arr;
}

10,000 × 10,000 grid = 100 million entries = ~800 MB RAM = mobile browser crashes.

NotationNameWhat it Means
O(1)ConstantYou use a few variables, no matter how big the input
O(log n)LogarithmicRecursion stack in binary search, tree height
O(n)LinearOne extra array/list/map of size n.
O(n²)Quadratic2D array, all pairs, distance matrix
O(2^n)ExponentialStoring all subsets, naive recursion without memo

Some Good Rules for Space -

  1. Never create a 2D array bigger than 1000 × 1000 unless you have a very good reason.

  2. On frontend: never store more than 10,000 complex objects in state.

  3. On backend: stream everything > 100 MB (CSV, JSONL, images).


4. Real Interview Questions That Look Innocent But Are O(n²) Traps

These problems feel like warm-ups - until the runtime exposes your solution. Each one has fooled thousands of developers.

  1. Two Sum
    Looks trivial. Most people brute-force with two loops = O(n²).
    Real fix: store complements in a hash map = O(n).
    Lesson: Hash maps are the default when you need repeated lookups.

  2. Longest Substring Without Repeating Characters
    Brute force checks all substrings = O(n²–n³).
    Real fix: sliding window + Set/last-seen index = O(n).
    Lesson: “Substring with constraints” usually means sliding window.

  3. Valid Sudoku Board
    9×9 seems tiny, leading to unnecessary nested loops.
    Better: track rows/cols/boxes with hash sets or bitmasks = effectively O(1).
    Lesson: Fixed-size inputs are constant time - interviewers notice this.

  4. Group Anagrams
    Sorting each string works but costs O(n × k log k).
    Faster: use character-frequency arrays as keys = O(n × k).
    Lesson: Sorting is fine, but counting is faster for anagrams.

  1. 3Sum / k-Sum
    Brute force is O(n³).
    Real fix: sort once, use two pointers = O(n²).
    Lesson: “Sort + two pointers” is the standard play for sum problems.

Moral:
If you find yourself adding a second or third nested loop, pause and ask:
Can a hash map, a sort, or a sliding window save me?


5. When NOT to Care About Big O (The Controversial Truth)

Yes, you read that right.

Here’s when Big O is completely irrelevant in 2025:

  1. n ≤ 100 = O(n³) is still microseconds

  2. Internal tools, admin dashboards, personal projects

  3. Startup with < 10,000 active users

  4. One-time scripts or data migrations

  5. Hackathons (correct > fast)

Real example: I once refactored a reporting script from O(n²) → O(n log n) and saved 400 ms. The script ran once per day at 3 AM. Nobody noticed. I wasted 4 hours.

Donald Knuth’s golden quote: “Premature optimization is the root of all evil.” But its lesser - known brother: “Premature pessimization (writing slow code from the start) is just as evil.”

Rule of thumb: Write the simplest correct code first → ship → measure → optimize only the parts that hurt.


6. Tools to Catch Slow Code Before Your Users Start Screaming

Stop guessing. Start measuring.

  1. Language-Agnostic Mindset

Performance isn’t a feeling - it’s data. Use the right profiler and identify the exact line or function that’s dragging your system down.

  1. JavaScript / Node.js -

Chrome DevTools → Performance Tab
Record a session and immediately see which functions are eating most of your CPU time.

clinic.js (Doctor)

npx clinic doctor -- node app.js

3.Python

cProfile (built-in profiler)

python -m cProfile -s cumulative script.py

Shows you exactly which functions dominate runtime.

4.Any Language (Quick Sanity Check)

LeetCode / HackerRank execution times
If your “Accepted” code runs in 2800 ms while top solutions run in 50 ms.
your code will definitely be slow in production.


Big-O Rules I Wish I Knew on Day 1 (The Golden 10)

  1. Nested loops over the same array → 99% chance you just wrote O(n²).

  2. Use the built-in sort. It’s optimized in C/C++ and almost always faster than a hand-rolled version.

  3. Hash Maps/Sets turn O(n²) problems into O(n) like magic.

  4. String concatenation inside a loop (Java, old Python) → sneaky O(n²).

  5. Space complexity hurts mobile apps more than time complexity. Memory is the real bottleneck.

  6. If n ≤ 500, stop overthinking Big-O. Go get coffee. Micro-optimizing tiny inputs is a waste.

  7. Try “sort + two pointers” before you consider three nested loops. 80% of interview problems collapse this way.

  8. Measure first, optimize later. Assumptions create bugs; profiling reveals truth.

  9. Your clever bit-manipulation trick is slower than a hash map 99% of the time. Cute ≠ fast.

  10. When in doubt, use a Set. It’s the default fix for unnecessary O(n) lookups.


Conclusion - The Only Thing That Matters

You don’t need to memorize every complexity class. You don’t need to become a walking CLRS textbook.

Big O isn’t about impressing interviewers. It’s about respect — respect for your users’ time, respect for your teammates’ sleep, and respect for your future self who won’t have to rewrite this mess under pressure.

So keep it simple:

  1. See nested loops → reach for a hash map

  2. See “substring” or “subarray” → think sliding window

  3. See anything that touches strings inside a hot loop → count frequencies or use two pointers

  4. See memory creeping up → ask if you really need to store it all

Happy coding. And may all your loops be O(n) or better.