<style> .reveal-up, .reveal-left, .reveal-right, .reveal-stagger > * { opacity: 1 !important; transform: none !important; } </style>
  1. Learn
  2. DSA
  3. Foundations
All lessons in this course

Lesson 3 of 50 13 minBeginner

Counting steps in real code

Work out the Big-O of real C# by looking at loop shapes, spot the hidden loops inside .NET methods, and know what input sizes each growth rate can handle.

You do not need maths to find the Big-O of most code. Look at the loops and ask how many times the innermost line runs. A few simple rules cover nearly everything:

Code shapeRuleResult
Plain statements, no loopsA fixed number of stepsO(1)
One loop over n itemsBody runs n timesO(n)
Loops one after anotherAdd them, keep the biggestO(n) + O(n) = O(n)
A loop inside a loopMultiply themO(n) × O(n) = O(n²)
A loop that halves (or doubles) its variableRuns log₂ n timesO(log n)
A method call inside a loopMultiply by the cost of the callloop × call

Check the rules with real numbers. This counter actually runs each loop and counts how often the inner line executes:

Interactive

Count the steps

Pick a loop shape. The counter really runs it for each n. Watch what happens when n doubles.

Steps.cs
for (int i = 0; i < n; i++)    for (int j = 0; j < n; j++)        steps++;
nstepsvs half n
864
16256× 4.00
10010,000
20040,000× 4.00
1,0001,000,000
2,0004,000,000× 4.00
O(n²). Nested loops MULTIPLY: n × n. Double n and the work goes up 4 times.

Inner loops that depend on the outer loop

When the inner loop starts at i + 1, it runs n−1 times, then n−2, then n−3… down to 0. That adds up to n(n−1)/2, about half of n². Half is a constant, so it is still O(n²). This shape appears whenever you compare every pair of items once.

Hidden loops in .NET methods

A single line can hide a whole loop. This is the most common reason that innocent-looking C# becomes slow:

Duplicates.cs
// Looks like one loop, but List.Contains is another loop inside → O(n²)static bool HasDuplicateSlow(List<string> ids){    var seen = new List<string>();    foreach (var id in ids)    {        if (seen.Contains(id)) return true;   // O(n) scan every time!        seen.Add(id);    }    return false;} // HashSet lookups take about one step each → O(n)static bool HasDuplicateFast(List<string> ids){    var seen = new HashSet<string>();    foreach (var id in ids)    {        if (!seen.Add(id)) return true;       // Add returns false if already there    }    return false;}

With 100,000 IDs the slow version does up to 5 billion string comparisons; the fast version about 100,000 hash lookups. Know the cost of the methods you call:

OperationCost
list[i], array[i], list.CountO(1)
list.Add(x)O(1) on average (amortised; see Dynamic arrays)
list.Contains(x), IndexOf, Remove(x)O(n): scans the list
list.Insert(0, x), list.RemoveAt(0)O(n): shifts every element
dict[key], TryGetValue, ContainsKey, HashSet.ContainsO(1) on average
Array.Sort, list.Sort, LINQ OrderByO(n log n)
LINQ Where, Select, Any, First, Count()O(n) when enumerated
.ToList(), .ToArray()O(n) time and O(n) memory
s = s + x in a loop (strings)O(n²) in total: copies the whole string each time. Use StringBuilder.

What input size can each growth rate handle?

A modern CPU does roughly 100 million simple steps per second. That gives a useful rule of thumb for “will this finish in about a second?”, and it is exactly how competitive programmers choose an algorithm from the input limits:

Largest n for ~1 secondAlgorithm must be
about 10O(n!)
about 20–25O(2ⁿ)
about 500O(n³)
about 10,000O(n²)
about 1–10 millionO(n log n)
about 100 millionO(n)
anythingO(log n) or O(1)
Quick check

What is the Big-O of this code? for (int i = 1; i < n; i *= 2) for (int j = 0; j < n; j++) count++;

Quick check

foreach (var b in beams) if (names.Contains(b.Name)) count++; where names is a List<string>. What is the cost, and what is the fix?

Practice

This method counts how many IDs from a also appear in b. Give its Big-O, then rewrite it to be O(a + b).

Show a hint

Which line hides a loop? Which structure answers “is it in there?” in one step?

Show the solution
Solution.cs
// Original: O(a × b) because List.Contains scans b each timestatic int CommonSlow(List<int> a, List<int> b){    int count = 0;    foreach (int x in a)        if (b.Contains(x)) count++;    return count;} // Faster: O(a + b)static int CommonFast(List<int> a, List<int> b){    var inB = new HashSet<int>(b);     // O(b), once    int count = 0;    foreach (int x in a)               // O(a)        if (inB.Contains(x)) count++;  // O(1) each    return count;} Console.WriteLine($"Common: {CommonFast([1, 2, 3, 4], [3, 4, 5])}");
Output
Common: 2

Remember

  • Loops one after another add; loops inside loops multiply; halving loops are O(log n).
  • A method call inside a loop multiplies the loop by the cost of the call.
  • List.Contains, IndexOf, Insert(0, …) and LINQ queries are hidden O(n) loops.
  • About 100 million simple steps per second: O(n²) is fine for 10,000 items, not for a million.
0 of 50 lessons completed