Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

After this page, you should be able to:

Builds on: Cases and bounds and shallow copies.

Code languages: Python defines the explanations, figures and self-checks. Use the language guide for native conventions and dependencies. Complete runnable topic examples: Python · JavaScript · Julia.

The comparison counts are shared. JavaScript returns multiple results in arrays, Julia in tuples; graph/node references and absence use each language’s conventions.

Loop syntax suggests where to look, but iteration counts and operation costs determine the result. Explain the count first, then simplify its growth.

Consecutive loops add their costs

Python
JavaScript
Julia
def count_two_passes(values):
    work = 0
    for value in values:
        work += 1
    for value in values:
        work += 1
    return work

print(count_two_passes(["KR", "MUS", "UTN"]))  # 6

For n items, the selected operation executes n + n = 2n times. Including setup and loop overhead does not change tight time Θ(n) under a unit-cost model. Two consecutive linear passes do not become quadratic.

Count the inner work in nested loops

Python
JavaScript
Julia
def count_pairs(n):
    full = 0
    for i in range(n):
        for j in range(n):
            full += 1

    triangular = 0
    for i in range(n):
        for j in range(i):
            triangular += 1
    return full, triangular

print(count_pairs(5))  # (25, 10)

The first nest performs n operations for each of n outer iterations: n² total. In the second, the inner range depends on i, so add the row lengths:

0+1+⋯+(n−1)=n(n−1)2.0+1+\cdots+(n-1)=\frac{n(n-1)}{2}.
Two five-by-five grids show 25 full-loop operations and 10 triangular-loop operations; each occupied cell corresponds to one pair of indices.

Both counts have tight quadratic growth. For n ≥ 2, the triangular count lies between n²/4 and n²/2. Shorter rows reduce the count but do not make it linear. For n = 0 or 1, its inner body executes zero times; asymptotic claims concern large n.

Nesting does not always mean Θ(n²). If an inner loop performs a fixed three operations per outer iteration, the count is 3n. A full scan of n rows and m columns visits nm cells; retaining both parameters exposes that dependence.

Repeated halving gives logarithmic iteration counts

Python
JavaScript
Julia
def halving_steps(n):
    if n < 1:
        raise ValueError("n must be positive")
    steps = 0
    remaining = n
    while remaining > 1:
        remaining //= 2
        steps += 1
    return steps

print(halving_steps(16))  # 4
print(halving_steps(13))  # 3
print(halving_steps(1))   # 0

Assume n is a positive integer. For 16, updates give 8, 4, 2, 1. For 13, floor division gives 6, 3, 1. After k updates, the remainder is floor(n/2ᵏ). The exact body count is floor(log₂ n), not generally ceil(log₂ n). Division and the stopping condition determine the rounding.

Time is Θ(log n) asymptotically with constant-cost arithmetic. Θ(1 + log n) also makes fixed work at n = 1 explicit. Here n is the starting numerical value; measured against its binary bit length b, the iteration count is Θ(b). The integer precondition is part of the example; the code is not a general-purpose numeric validator.

Count the branch that runs

Python
JavaScript
Julia
def inspect_count(values, detailed):
    if not values:
        return 0
    if not detailed:
        return 1
    return count_two_passes(values)

This calls count_two_passes from the first example on this page. For a nonempty list, the non-detailed path takes fixed time, while the detailed path traverses it twice. Over both choices of detailed, best-case time is Θ(1) and worst-case time is Θ(n). If detailed is fixed by the specification, analyse that restricted input family.

Alternative branches are not automatically added as if both ran. Conditions and all work along the selected path still count.

Check helper inputs, returns and dependencies

These are analysis examples under explicit preconditions, rather than general-purpose validating APIs. List arguments are finite and remain unchanged; n is a nonnegative integer for count_pairs, a positive integer for halving_steps, and detailed is boolean for inspect_count. Passing other types or invalid sizes is outside the stated examples unless an explicit error is shown.

FunctionResult / mutationEmpty or boundary behaviour
count_two_passes(values)Return 2n; observe the listEmpty list returns 0
count_pairs(n)Return (n², n(n−1)/2); no input mutationn=0 returns (0, 0); n=1 returns (1, 0)
halving_steps(n)Return floor(log₂ n); no input mutationn=1 returns 0; n<1 explicitly raises ValueError
inspect_count(values, detailed)Return 0 for empty, 1 for non-detailed, otherwise 2nRun count_two_passes above first; this is a helper dependency
count_values(values)Return n; observe the listEmpty list returns 0
copy_values(values)Return a new shallow outer list; observe the originalEmpty list returns a new empty list

The two-pass count assumes a reusable list. Replacing it with a single-use iterator would consume it in the first pass and change the result. The returned list from copy_values has its own slots while sharing references to the original elements.

Time and space measure different resources

Space complexity describes storage as size grows. Distinguish existing input, returned output and additional working storage. Count the maximum live storage, not every allocation that ever occurred.

State whether output storage is included

Python
JavaScript
Julia
def count_values(values):
    count = 0
    for value in values:
        count += 1
    return count

def copy_values(values):
    result = []
    for value in values:
        result.append(value)
    return result
FunctionTimeWorking storage excluding input/outputOutput storage
count_valuesΘ(n)Θ(1) wordsΘ(1) words
copy_valuesΘ(n)Θ(1) wordsΘ(n) references

Here list append is amortised constant time, and a counter or reference occupies one word in our model. The copy creates a new outer list, not copies of every object; see shallow copying.

In this book, auxiliary space excluding the input includes newly allocated result storage, unless explicitly stated otherwise. Thus copy_values uses Θ(n) auxiliary space. If output is excluded, report its Θ(1) additional working space and Θ(n) output separately. Make the convention explicit.

Under a bit-level model, a counter up to n needs Θ(log n) bits, rather than one word. These introductory analyses use the stated word model. Recursive calls also consume working storage through the call stack, developed in Topic 3.

Write a complete complexity claim

Define input size, choose a resource, specify the case and cost assumptions, derive the count, and state the most informative justified bound.

For example: “For n list elements with constant-cost equality, linear search uses n target comparisons in the worst case, giving tight time Θ(n), and maintains Θ(1) extra words.” This explains more than “the code is Big-O”.

Compare costs for the actual listings

ExampleTime under the stated word/operation modelWorking storage excluding input/output; returned storage
Two full list passesΘ(n+1)O(1) words; O(1) words
Full and triangular pair counts togetherΘ(n²+1)O(1) words; O(1) words
Floor-halving positive integer nΘ(1+log n)O(1) words; O(1) words
Non-detailed inspectionΘ(1)O(1) words; O(1) words
Detailed inspection of a listΘ(n+1)O(1) words; O(1) words
Count list valuesΘ(n+1)O(1) words; O(1) words
Shallow list copyΘ(n+1), including amortised append allocationO(1) logical workspace; Θ(n+1) returned slots

The added constant makes empty/singleton setup visible; it does not change the growing-input class. Python range objects do not allocate all loop indices in advance. Counters and loop indices are counted as words here; Python big-integer bit costs require a more detailed analysis.

The copy’s table treats its dynamic-list backing storage as output space. During an occasional resize, old and replacement backing arrays can coexist temporarily, still within O(n+1) total additional space. A fixed count of local variable names therefore does not imply a fixed amount of live storage.

If a task first constructs or sorts its representation, include that preparation in the complete task’s time and peak storage. Keeping multiple parameters, such as rows and columns, can be more informative than turning every loop into a claim about one n.

Check your understanding

Predict the count or assess the claim before opening the explanation.

1. For count_pairs(5), why are the counts 25 and 10 rather than 25 and 15?

Show explanation

The triangular inner range is range(i) for i=0 through 4, so its row lengths are 0,1,2,3,4. They sum to 10.

2. halving_steps(13) makes how many updates, and does copy_values use constant auxiliary space under this book’s default convention?

Show explanation

Three updates: 13→6→3→1. The copy uses linear auxiliary space because the default convention includes its new output list; its logical workspace excluding output is constant.

Next: Correctness and proof habits.

Browse: Analysing algorithms · Concept index