Skip to content
DSA complexity 8 min read

Analyzing the Complexity of Loops

Analyzing iterative code means deriving its Big-O by counting how many times its loops run as a function of input size n. The whole skill reduces to a few rules: count one loop’s iterations, multiply nested loops, add sequential loops, and watch for loops that halve or grow the range. Master these and you can read the complexity off almost any function at a glance.

Rule 1: a single loop over n is O(n)

If a loop runs once per element, the work is linear. The body’s constant work (a few operations) doesn’t change the class.

long long sum(const std::vector<int>& a) {
    long long total = 0;
    for (int x : a) total += x; // runs n times -> O(n)
    return total;
}

Rule 2: nested loops multiply

A loop inside a loop runs outer × inner times. Two independent loops each over n give O(n²).

long long pairSum(const std::vector<int>& a) {
    long long s = 0;
    for (size_t i = 0; i < a.size(); i++)        // n
        for (size_t j = 0; j < a.size(); j++)    // n
            s += a[i] * a[j];                    // n*n = O(n^2)
    return s;
}

The triangular nested loop is still O(n²)

When the inner loop starts at i, it runs n + (n-1) + ... + 1 = n(n+1)/2 times total. That’s O(n²) — half the work of the full square, but Big-O drops the constant ½.

int countPairs(const std::vector<int>& a) {
    int c = 0;
    for (size_t i = 0; i < a.size(); i++)
        for (size_t j = i + 1; j < a.size(); j++) // ~n*(n-1)/2 total
            c++;
    return c;
}

Rule 3: sequential loops add

Two loops one after the other are O(n) + O(n) = O(n). We keep only the dominant term, so an O(n) loop followed by an O(n²) loop is just O(n²).

Rule 4: a halving loop is O(log n)

If the loop variable is multiplied or divided each step (instead of +1), it reaches the bound in logarithmic steps. Dividing n by 2 repeatedly hits 1 after about log₂ n steps.

int countHalvings(int n) {
    int steps = 0;
    while (n > 1) { n /= 2; steps++; } // O(log n)
    return steps;
}

A loop that grows a variable toward n (i *= 2 until i >= n) is also O(log n) for the same reason.

Going deeper: A halving loop nested inside a loop over n gives O(n log n) — the complexity of efficient sorting. And distinct inputs multiply separately: looping n times over one array and m times over another (independent sizes) is O(n + m) sequentially or O(n · m) if nested. Don’t collapse separate inputs into a single n.

A worked walkthrough

void mystery(const std::vector<int>& a) {
    int n = a.size();
    for (int i = 0; i < n; i++) { /* A: n */ }
    for (int i = 0; i < n; i++)
        for (int j = 0; j < n; j++) { /* B: n^2 */ }
    for (int k = 1; k < n; k *= 2) { /* C: log n */ }
}

Add the parts: n + n² + log n. The dominant term wins, so this is O(n²).

For beginners: When in doubt, count the loops. One loop → O(n). Loop in a loop → O(n²). A loop that multiplies/divides its counter → O(log n). Loops in sequence → add (keep the biggest).

Next steps

Loops are only half the story. For functions that call themselves, you need recurrences and recursion trees — see analyzing recursive code. Keep the complexity cheat sheet nearby, then practice on the complexity exercises.

Last updated June 25, 2026
Was this helpful?