Motivation
Saying that one program “runs faster” is incomplete until we say what is being measured, how input size is defined, and which legal inputs are being compared. The lecture deck motivates this point with selection sort: on one recorded machine, doubling the array size made the measured time roughly four times as large. Those timings are useful evidence about scaling, but they are not a proof. Hardware, compiler choices, memory behavior, and finite-input constants all affect a stopwatch result.
Complexity analysis replaces the machine-specific question with a mathematical one. We define a cost function , count selected basic operations, and ask how that count grows as becomes large. The result is meaningful only under the stated model. An array comparison may be constant time when keys have fixed size, for example, but comparing arbitrarily long strings has its own cost.
This unit develops the notation and counting rules needed for that analysis. The goal is not to attach a familiar label to code by sight. It is to write an exact count or a justified bound first, then state a tight asymptotic class when the evidence supports one.
Definitions
Definition
Input size and the RAM cost model
For an input , let be the declared size parameter and let be the number of charged basic operations. Unless a different model is stated, this note uses a unit-cost RAM model: fixed-width arithmetic, index arithmetic, array access, assignment, and comparison of fixed-size keys each cost one constant-size unit. The cost function must also state a case, such as the maximum over all legal inputs of size for worst-case cost.
The model separates time from auxiliary space. A claim about running time does not automatically describe memory use, and the cost of a helper call must be included rather than treated as one source-code line.
Definition
Asymptotic upper, lower, and tight bounds
Let be eventually nonnegative, with eventually positive.
- if there are constants and such that for every .
- if there are constants and such that for every .
- if both bounds hold: there are and such that whenever .
Big-O is an eventual upper bound, not a unique name for a growth rate. A linear function is also . When we mean that a function genuinely grows at the same asymptotic rate as , the informative statement is .
Definition
Case labels, expectations, and amortized cost
Best-case and worst-case costs take the minimum and maximum over legal inputs of size . Average-case cost is an expectation under an explicitly stated input distribution. Amortized cost instead averages over a specified sequence of operations without assuming random inputs. For example, start with an empty dynamic array whose initial capacity is a fixed positive constant, and whenever it fills, replace its capacity by a fixed factor times the old capacity (with consistent integer rounding). In an insertion-only sequence of pushes, charge constant cost for an ordinary push and one unit for every element copied during resize. One resizing push can cost , but the sequence has total cost, giving amortized cost per push.
From code to a cost function
A defensible analysis follows a repeatable sequence. First, state the legal inputs and choose the size parameter. For an array routine this is often its length, but a graph routine may need both and , and a numerical routine may depend on the number of input bits rather than the numeric value itself. Compressing a genuinely two-parameter problem into one symbol can hide the case that dominates the cost.
Second, name the operation being charged. Selection sort is especially clean when key comparisons are counted, because its loop bounds determine that count independently of the input order. If assignments, allocations, or key-copying costs are also relevant, write separate counts and combine them only after their models are clear. A statement such as “the loop costs ” is incomplete unless it identifies what happens on each of those executions.
Third, translate control flow into arithmetic before simplifying it. Consecutive blocks contribute a sum. A fixed-cost body repeated over a rectangular iteration space contributes a product. A bound that depends on the outer index contributes a sum such as . A recursive routine contributes a recurrence only after the number and sizes of recursive calls, plus the non-recursive work, have been justified.
Finally, prove both sides when claiming a tight class. An upper bound alone can be deliberately loose; a matching lower bound shows that the chosen representative function is unavoidable for the implementation and case being analyzed. This derivation-first discipline also exposes hidden assumptions, such as constant-time indexing, a bounded key size, or a helper whose own scan was mistakenly counted as one operation.
For any fixed bases , , so changing the logarithm base changes only a constant factor. For fixed parameters , , and , the source hierarchy can therefore be read as
with the order interpreted through ratios or tight classes, not through Big-O labels alone. In particular, and for fixed positive parameters.
Read and try
Compare asymptotic growth at one n
The widget now ties each growth class to a concrete code shape, so readers can change n and see how the chosen sample behaves against the comparison table.
Choose an algorithm shape
Code sample
for (int width = 1; width < n; width *= 2) {
for (int i = 0; i < n; i += 2 * width) {
merge_block(i, width);
}
}Growth class: O(n log n)
Estimated primitive steps: 64.00
Interpretation: There are about log n rounds, and each round still touches a linear amount of data.
| Class | Value at n=16 |
|---|---|
| O(1) | 1.00 |
| O(log n) | 4.00 |
| O(n) | 16.00 |
| O(n log n) | 64.00 |
| O(n^2) | 256.00 |
Comparing growth classes responsibly
The hierarchy describes eventual growth, not the running time at every finite input. A quadratic implementation with a small constant can outperform a linear one for a limited range, and cache behavior can shift the crossover again. Asymptotic notation intentionally discards those fixed constants so that the long-run scaling can be compared independently of one machine. Engineering decisions should therefore use both the asymptotic result and measurements over the input range that actually matters.
A ratio gives a precise comparison when both costs have tight positive bounds. If , then has smaller order than ; this is the meaning of . If the ratio approaches a positive finite constant, the functions belong to the same class, although their exact costs may differ. If the ratio grows without bound, has larger order. This method is safer than trying to rank two set-membership statements such as and , because either statement may be a deliberately non-tight upper bound.
Theorem/Proposition
Theorem
A positive-leading polynomial has dominant-term growth
Let , where is a nonnegative integer, the coefficients are fixed real constants, , and is eventually nonnegative. Then .
Theorem
Selection sort makes a triangular number of comparisons
For , consider the displayed selection-sort implementation on a random-access array. In the unit-cost model, each key comparison costs . The algorithm makes exactly such comparisons on every input and therefore takes time. The conditional swap executes at most times and does not change the tight bound.
Proof sketch or proof idea
Proof
Proof of dominant-term polynomial growth
If , then for every , so . Now assume . For , every lower power with is at most . Hence
which gives the required upper bound. For the lower bound, divide the lower terms by . Each ratio tends to zero, so there is a threshold after which . Therefore for . Positive constants now bound above and below by , proving . The sign and fixed-coefficient assumptions prevent “drop the lower terms” from becoming an unsafe cancellation rule.
Proof
Proof of the selection-sort comparison count
On outer pass , the inner loop uses , so it performs comparisons. Pass performs , and pass performs one. The loop bounds do not depend on the key order, so the count is identical on sorted, reverse-sorted, and arbitrary arrays. Thus
For , this count lies between and , establishing comparisons. Constant-time loop control and at most linear many swaps add only work, while the comparisons already give an lower bound for this implementation.
The familiar lower bound for general comparison sorting has a different scope: it assumes a comparison decision-tree model and arbitrary orderable inputs. Its proof, together with quickselect, counting sort, and radix sort, belongs to the later sorting note. It should not be used here without those model assumptions.
Worked examples
Worked example
Compare the lower-order term with the proposed dominant term:
Equivalently, grows without bound. The conclusion is not that the linear term vanishes from an exact formula. It is that the linear term becomes negligible relative to , which supports a tight statement when the leading coefficient is positive.
Worked example
Constant-time statements
int x = a + b;
int y = x * 2;
return y;
Under the stated fixed-width RAM model, each statement executes once and costs
constant time. Their sequential costs add to another constant, so the fragment
is . This conclusion would need revision if, for example, a and b
represented unbounded integers whose arithmetic cost grows with their bit
length.
Worked example
Linear scan
int sum(const int a[], int n) {
int total = 0;
for (int i = 0; i < n; i++) {
total += a[i];
}
return total;
}
The body runs exactly times and has constant cost, so the total is
for fixed constants and . The lecture deck's
Average function has the same structure: one full pass plus a final division.
Calling such a helper is therefore a operation, not an automatically
constant-cost source line.
Worked example
Nested loops create quadratic cost
int countPairs(int n) {
int c = 0;
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
c++;
}
}
return c;
}
For this particular program, the inner statement runs exactly times, so the cost is . The conclusion follows from the bounds, not merely from seeing two loop keywords.
Function calls can create the same product invisibly. In the source deck's
naive variance routine, an -iteration loop calls Average(array,n) on every
iteration. A helper repeated times gives work;
the final average adds only . Computing the mean once before the loop
changes the composition to several consecutive linear passes, whose costs add
to . If temporary storage is used, its auxiliary-space cost must be
reported separately.
The same counting method handles loops that are not rectangular. If the inner
loop runs from i + 1 to n - 1, its execution count is
, a triangular sum. If an index doubles on every
iteration, the condition gives executions. If the
inner bound is i, the count is rather than times a fixed
quantity. Thus the relevant object is the iteration space described by the
bounds, not the number of syntactic loop headers.
Worked example
Selection-sort growth
void selectionSort(int a[], int n) {
for (int i = 0; i < n - 1; i++) {
int min = i;
for (int j = i + 1; j < n; j++) {
if (a[j] < a[min]) min = j;
}
if (min != i) {
int tmp = a[i];
a[i] = a[min];
a[min] = tmp;
}
}
}
The exact comparison count is , not an approximation. Replacing by changes the leading quadratic expression by a factor approaching four; replacing it by gives a factor approaching one hundred. Those ratios explain the lecture timing pattern, while the theorem—not the timing table—proves the asymptotic class.
Worked example
A clean simplification proof
To prove , choose and . For every ,
For a tight result, also note . Thus the expression is . Giving both inequalities makes clear why would be true but uninformative.
Worked example
Binary search does not need a full scan
int binarySearch(const int a[], int n, int target) {
int left = 0, right = n - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (a[mid] == target) return mid;
if (a[mid] < target) left = mid + 1;
else right = mid - 1;
}
return -1;
}
The precondition is a sorted random-access array with constant-time key
comparisons. A first-probe match gives best-case . In the worst case,
each iteration leaves at most half the candidates, so after iterations at
most remain. Reaching one candidate requires
. The midpoint formula avoids overflowing left + right.
An unsuccessful search has the same logarithmic worst-case bound: the candidate interval keeps shrinking until it becomes empty. If duplicate keys are allowed, this version may return any matching position; finding the first or last match requires a modified invariant. Binary search also does not make an unsorted search logarithmic for free. Sorting the data first costs additional work, so that preprocessing is justified only when its cost can be shared across enough later queries. On a linked list, locating the midpoint is not constant time, so the random-access assumption is part of the complexity claim rather than an implementation detail.
Worked example
Why merge-style structure gives n log n
Suppose an algorithm splits a problem in half until subproblems have size one, and every recursion level performs total combination work. There are levels, so the level costs add to . Equivalently, the recurrence has that solution for powers of two, with ceilings and floors changing only constants for general .
The premise “linear work per level” must be proved. Divide-and-conquer alone does not guarantee an bound.
This level argument counts time, not automatically space. If the two recursive calls are completed one after the other, the active recursion depth is , while temporary merge storage may still require auxiliary space. A different implementation or parallel execution can change the space profile without changing the recurrence used for total work, so the resource being bounded must remain explicit.
Common mistakes
Common mistake
Using Big-O as though it were a tight class
Because is both and , two Big-O memberships cannot by themselves prove that one algorithm eventually outgrows another. Compare the actual functions or establish bounds first.
Common mistake
Counting loop syntax instead of executions
Two independent length- loops nested as in countPairs give body
executions. Consecutive loops add to , a triangular inner bound sums to
, and a halving loop has logarithmically many iterations. Write the
sum or product before simplifying.
Common mistake
Ignoring preconditions, helper costs, or the quoted case
Binary search requires sorted random-access input; average case requires a distribution; a helper call contributes its full cost. Every conclusion should name the legal input, cost model, and best, worst, expected, or amortized case.
Common mistake
Dropping terms as blind algebra
Lower-order terms remain in the exact cost and can matter for finite . Dominant-term simplification is justified by inequalities or ratios under fixed coefficients and eventual nonnegativity, not by deleting arbitrary signed or -dependent terms.
Common mistake
Calling every dynamic-array push constant-time
A push that triggers a resize can cost . Under geometric capacity growth, a specified sequence of pushes has amortized cost per push; that does not make every individual push worst-case constant.
Summary
- Define , the charged operation, the resource, and the input case before writing a complexity label.
- is an upper bound, a lower bound, and a tight bound.
- Fixed logarithm bases differ only by constants; fixed powers dominate logs, and fixed-base exponentials dominate powers.
- Count sequential work by addition, repeated independent work by products, dependent bounds by sums, and recursive work by a recurrence or level count.
- Selection sort makes exactly comparisons and is in the stated model; empirical timings only illustrate that result.
Exercises
Checkpoint
If an algorithm has two independent nested loops that each run n times, what is the tight class of the total cost?
Assume the loop body is and always executes.
Checkpoint
Why does a Theta(n log n) cost grow more slowly than a Theta(n^2) cost for sufficiently large n?
Compare the representative functions through their ratio.
Checkpoint
What is the tight asymptotic class of 0.0001n^3 + n?
Use the positive-leading polynomial theorem, not only a deletion slogan.
Checkpoint
Why does selection sort have quadratic cost even though only one element is placed each pass?
Count the comparisons in the shrinking inner loop.
Solutions
Solution · Answer 1
The body executes times, so the total cost is under the stated assumptions. The independence and fixed-cost premises are essential.
Solution · Answer 2
The ratio is , which tends to zero. With positive tight bounds, this shows the quadratic cost eventually grows faster.
Solution · Answer 3
The leading coefficient is positive and the remaining term has lower degree, so the polynomial is . It is also , but that weaker upper bound does not identify its tight growth.
Solution · Guided solution 4
Passes make comparisons. Their sum is , independent of input order. Placing one element per pass does not eliminate the scan needed to choose that element.