Free Ebook cover Programming Logic Foundations: Thinking Like a Programmer

Programming Logic Foundations: Thinking Like a Programmer

New course

10 pages

Complexity Intuition: Comparing Approaches by Work and Resources

Capítulo 9

Estimated reading time: 8 minutes

+ Exercise

What “complexity” means in practice

When you compare two approaches, you are usually comparing how their work and resource use grow as the input size grows. You do not need heavy math to build good intuition; you need a habit of counting at a high level:

  • Work (time): roughly how many basic steps happen (comparisons, additions, assignments, lookups, swaps, reads/writes).
  • Resources (space): how much extra memory is needed beyond the input (extra arrays, hash tables, recursion stack, buffers).

We will use n for “number of items” (records, numbers, characters). The goal is not exact instruction counts, but a reliable estimate: “about one pass over the data” vs “about one pass per item” vs “about repeated halving.”

Counting operations without deep math

Step 1: Choose a unit of work

Pick a simple unit that matches the task. Examples:

  • Searching: “one comparison with an item”
  • Summing: “one addition per item”
  • Sorting: “one comparison between two items” (plus swaps/moves)
  • String processing: “one character check”

Be consistent when comparing two approaches to the same task.

Step 2: Count per item, per pass

Many algorithms are easiest to estimate by answering:

Continue in our app.

You can listen to the audiobook with the screen off, receive a free certificate for this course, and also have access to 5,000 other free online courses.

Or continue reading below...
Download App

Download the app

  • How many passes over the data?
  • How many steps per item per pass?

Example: “two passes, each doing ~3 operations per item” is about 6n operations.

Step 3: Identify growth patterns

Common growth patterns you can recognize by structure:

PatternIntuitionTypical structure
ConstantDoesn’t grow with nFixed number of steps
LinearOne pass over itemsSingle loop over n
QuadraticWork per item grows with nNested loops over n
LogarithmicRepeatedly halves the problemBinary search, divide-and-conquer steps
LinearithmicLinear work across log-sized levelsMany efficient sorts, merge-like processes

You do not need to memorize names; focus on the story: “one pass,” “one pass per item,” “keep halving.”

Comparing growth with small input-size examples

A practical way to compare approaches is to plug in a few input sizes and estimate step counts. Suppose:

  • Approach A does about 5n steps (linear).
  • Approach B does about n^2 steps (quadratic).
nA: 5nB: n²Which is smaller?
1050100A
10050010,000A
1,0005,0001,000,000A by far

Even if constants differ (maybe B has a smaller constant factor), the growth pattern often dominates as n becomes large.

Side-by-side comparison 1: Finding a number in a list

Task

Given a list of n numbers and a target value, decide whether the target exists.

Approach A: Linear scan (unsorted list)

for each item in list:        // n items (one pass)  if item == target: return foundreturn not found

High-level count:

  • Worst case: check all n items → about n comparisons.
  • Best case: first item matches → about 1 comparison.

Space: constant extra memory.

Approach B: Binary search (sorted list)

low = 0, high = n-1while low <= high:              // each loop halves range  mid = (low+high)//2  if list[mid] == target: return found  else if list[mid] < target: low = mid+1  else: high = mid-1return not found

High-level count:

  • Each iteration cuts the remaining search space roughly in half.
  • Number of iterations is “how many times can you halve n until you get to 1?” (logarithmic growth).
  • For intuition: n=1,024 halves to 1 in about 10 steps.

Space: constant extra memory.

Trade-offs

  • If the list is already sorted (or you will do many searches), binary search scales much better.
  • If you only do one search and the list is unsorted, sorting just to search may cost more overall than a single linear scan.

Side-by-side comparison 2: Detecting duplicates

Task

Given a list of n items, determine whether any value appears more than once.

Approach A: Compare every pair (no extra memory)

for i from 0 to n-1:  for j from i+1 to n-1:    if list[i] == list[j]: return duplicate foundreturn no duplicates

High-level count:

  • For each item, compare it to many later items.
  • Roughly (n-1) + (n-2) + ... + 1 comparisons, which grows like .

Space: constant extra memory.

Approach B: Track seen items with a set (extra memory)

seen = empty setfor each item in list:            // one pass  if item in seen: return duplicate found  add item to seenreturn no duplicates

High-level count:

  • One pass over n items.
  • Each membership check and insert is typically “about constant time” on average.
  • Total work grows roughly like n.

Space: extra memory for up to n items in the set.

Trade-offs

  • If memory is tight, the pairwise approach uses almost no extra space but can become very slow as n grows.
  • If speed matters and memory is available, the set-based approach usually scales far better.

Side-by-side comparison 3: Counting frequencies

Task

Given n words, produce a frequency table (word → count).

Approach A: For each word, scan a growing list of counters

counts = empty list of (word, count)for each word w in input:  found = false  for each entry e in counts:      // may grow up to n    if e.word == w:      e.count += 1      found = true      break  if not found: append (w, 1)

High-level count:

  • Outer loop runs n times.
  • Inner scan can be small at first but can grow toward n.
  • Worst case (all words distinct): about 1 + 2 + ... + (n-1) comparisons → grows like .

Space: stores up to n counters.

Approach B: Use a map/dictionary

counts = empty mapfor each word w in input:          // one pass  if w not in counts: counts[w] = 0  counts[w] += 1

High-level count:

  • One pass over n.
  • Each lookup/update is typically about constant time on average.
  • Total work grows roughly like n.

Space: stores up to n keys and counts.

When “faster growth” can still be acceptable

Sometimes an approach with worse growth is still fine because constraints are small or because it is simpler to implement and less error-prone.

Use constraints to decide

Suppose you have two options:

  • Option 1: comparisons, minimal memory.
  • Option 2: 10n operations, but needs extra memory proportional to n.

If n is at most 200, then n² = 40,000 comparisons might be perfectly acceptable. If n can be 2,000,000, then is impossible, and you must choose a near-linear approach (or change the problem setup).

Constant factors and hidden costs

High-level counting ignores some real costs:

  • Memory allocation and cache behavior can make “extra space” slower or faster depending on access patterns.
  • Hash-based structures are usually fast on average, but have overhead per operation.
  • Sorting has a cost, but can enable many fast queries afterward.

Still, growth patterns are the first filter: they tell you what will break as n scales.

Practical step-by-step: Estimating work for an approach

Example: “Two passes plus a nested loop”

Imagine an algorithm that:

  • Pass 1: scans all items once doing ~2 operations per item → about 2n
  • Pass 2: scans all items once doing ~3 operations per item → about 3n
  • Then: for each item, compares it to every item (nested loop) → about

Total estimate: 2n + 3n + n². For large n, the term dominates, so the overall growth behaves like quadratic.

Example: “Preprocess then answer many queries”

You have n items and q queries.

  • Approach A: For each query, scan the list → about q * n comparisons.
  • Approach B: Build an index (e.g., set/map) once → about n inserts, then each query is ~constant → about n + q.

If q is large, preprocessing is usually worth it. If q is tiny, scanning may be simpler and good enough.

Exercises: Choose the better-scaling approach

For each exercise: (1) estimate step counts using “per item” and “per pass,” (2) decide which scales better as input grows, (3) mention any resource constraints (memory, preprocessing time, number of queries).

1) Membership queries: scan vs preprocess

You have a list of n product IDs and you must answer q questions: “Is ID X in the list?”

  • Approach A: For each query, scan the list from start to end.
  • Approach B: Build a set of all IDs once, then answer each query by checking the set.

Estimate work for n=100,000 and (a) q=10, (b) q=1,000,000. Which approach is better in each case, and why?

2) Duplicate detection under memory limits

You must detect duplicates in a list of n integers. You are told extra memory must be under 1 MB.

  • Approach A: Use a set to track seen values.
  • Approach B: Compare every pair (nested loops).

Assume n might be 50,000. Which approach is feasible under the memory constraint? If neither is feasible, explain what the constraint suggests about needing a different strategy (e.g., external storage, sorting in-place, or chunking).

3) Sorting to enable faster repeated operations

You need to answer q queries: “How many numbers are ≤ X?” on a list of n numbers.

  • Approach A: For each query, scan all n numbers and count.
  • Approach B: Sort once, then for each query use a binary-search-like method to find the boundary.

Use step estimates to compare q*n versus “sort cost + q times halving.” For n=1,000,000, compare q=5 and q=50,000.

4) Frequency counting: list of counters vs map

You process n words and want counts per word.

  • Approach A: Keep a list of (word,count) pairs and linearly search it for each word.
  • Approach B: Use a map/dictionary keyed by word.

Consider two cases: (a) almost all words are distinct, (b) there are only 100 distinct words repeated many times. Estimate the work in each case and explain how the number of distinct keys affects Approach A.

5) Nested loops with early exit

An algorithm checks whether any pair of items violates a rule. It uses nested loops but stops immediately when it finds a violation.

  • Case A: Violations are common and usually found quickly.
  • Case B: Violations are rare and usually not present.

Explain how the same code can behave very differently depending on input, and give a high-level estimate of best-case vs worst-case work.

Now answer the exercise about the content:

You must answer q membership queries (“Is X in the list?”) on a list of n IDs. Which strategy scales best as q becomes very large, and why?

You are right! Congratulations, now go to the next page

You missed! Try again.

Preprocessing into a set costs about one pass over n, then each query is typically constant time on average. As q grows, n + q scales far better than scanning n items per query (q*n).

Next chapter

Capstone Logic Workshop: From Specification to a Verified Procedure

Arrow Right Icon
Download the app to earn free Certification and listen to the courses in the background, even with the screen off.