Algorithms and Resource Pressure

LESSON

Computer Science Great Ideas

003 25 min beginner

Algorithms and Resource Pressure

Suppose a service receives a list of account identifiers and must report the first duplicate. For a list of five entries, almost any reasonable program feels instant. For a list of ten million entries, the procedure—not the programming language’s reputation—decides whether the job finishes comfortably.

By the end of this lesson, you should be able to:

An algorithm is not judged only by whether it returns the right answer. It is also judged by how its work and memory use change as the input grows.

Core Insight

Resource pressure appears when an input that was once small becomes ordinary: a nightly import, a search index, a list of payments, or a stream of sensor readings. Two programs can be equally correct on every input and still have radically different futures.

To make that future visible, choose a unit of work. Here, count identifier comparisons or membership checks. Then call the number of input items n and ask how that count changes when n doubles. This is a model: deliberately simpler than a real machine, but useful because it exposes the dominant pattern.

The notation O(n²) says that work grows proportionally to the square of the input size, up to constant factors and smaller terms. O(n) says it grows roughly in step with the input. Those are claims about growth, not promises that one particular run takes a particular number of milliseconds.

The Small Situation

Consider this list, read from left to right:

AVA-12, MIKA-04, LEO-19, MIKA-04, SANA-77

We want the first identifier that has already appeared. One direct procedure is: for each identifier, compare it against every earlier identifier. When we reach the second MIKA-04, the comparison with the earlier MIKA-04 reveals the duplicate.

This procedure has attractive properties. It needs almost no extra storage, is easy to write, and does not rely on a special data structure. On a short list, it is a perfectly sensible choice. The question is what happens on a long list with no duplicate, because that forces the procedure to do all of its possible work.

A Better Model: Count the Work

For the first item there are zero earlier items to compare. For the second there is one; for the third there are two; and so on. With no duplicate, the total comparisons are:

0 + 1 + 2 + ... + (n - 1) = n(n - 1) / 2

The exact formula matters here because it makes the pattern concrete.

Items n Pair comparisons in the no-duplicate case
5 10
100 4,950
1,000 499,500
10,000 49,995,000

If n doubles, this quantity becomes close to four times as large. The in O(n²) is a compressed way of naming that shape. We ignore the division by two when comparing very large inputs because it does not change the crucial fact: every additional item can cause comparisons with many earlier items.

The Pair-by-Pair Procedure

Here is the procedure as pseudocode:

for each position i from left to right:
    for each earlier position j before i:
        if identifier[i] equals identifier[j]:
            return identifier[i]
return NONE

Trace it on the sample list. AVA-12 has no earlier item. MIKA-04 is compared with AVA-12. LEO-19 is compared with both earlier entries. At the fourth position, the program compares MIKA-04 with AVA-12, then with the earlier MIKA-04, and returns it.

Early success can make this look cheap. That is real: algorithms can have different best, average, and worst cases. But an operational decision also needs to consider the expensive cases. An import system cannot assume that every bad input conveniently reveals its duplicate near the front.

The pair-by-pair method uses O(1) additional memory beyond the input: it stores a few positions and the current values. Its price is repeated work. It keeps asking, “Have I seen this before?” by rebuilding the answer from comparisons already made.

A Second Procedure: Remember What We Saw

Instead, keep a set named seen. A set records whether a value is present. For each identifier, first ask whether it is already in the set; if it is, return it. Otherwise add it and continue.

seen = empty set
for each identifier from left to right:
    if identifier is in seen:
        return identifier
    add identifier to seen
return NONE

On the example, add AVA-12, MIKA-04, and LEO-19. When the second MIKA-04 arrives, its membership test succeeds immediately. The procedure returns without comparing it with every earlier value one by one.

With a typical hash-based set and well-behaved hashing, membership and insertion are expected to take approximately constant time on average. Repeating that work once per input item gives expected O(n) time. The gain comes from saved history: the set may contain up to n identifiers, so it uses O(n) additional memory.

“Expected” is an important word. A hash table is not magic and O(1) is not an unconditional law. Poor hash behavior, adversarial inputs, resizing, or a different set implementation can change the costs. A balanced-tree set, for example, often gives O(log n) membership rather than expected constant time, while preserving a different set of guarantees.

What the Model Lets Us Predict

The model now supports a useful prediction. If a no-duplicate list grows from 10,000 to 20,000 items, the pair-by-pair comparison count grows from about 50 million to about 200 million—nearly four times. The set-based procedure processes roughly twice as many items and retains roughly twice as many identifiers.

This prediction is often enough to narrow the design decision before benchmarking. It says where a problem will become painful. It does not remove the need to measure a production system: identifier length, allocation costs, cache behavior, database access, and language runtime can all dominate at a particular scale.

Think of Big O as a map with the terrain simplified. It helps distinguish a road that gets four times longer when the trip doubles from one that gets twice as long. It does not tell you about every traffic light.

Time, Memory, and Assumptions

Neither procedure is universally superior. A useful choice names the constraints.

There is also a distinction between input memory and additional memory. The pair-by-pair version can reread the supplied list; the set version allocates a new summary of the values. Calling the latter “O(n) memory” is not a condemnation. It is a statement of what resource is being exchanged for less time.

Before changing an implementation, make the model testable. Record a representative input size, whether duplicates tend to appear early, the available memory budget, and the operation that actually dominates the service. Then benchmark both procedures with those conditions. If the measurements disagree with the model, investigate rather than discarding either one: perhaps reading the data dominates, perhaps the input is almost always tiny, or perhaps allocations and cache misses are the real pressure. Analysis tells you which questions to ask; measurement tells you how this particular system answers them.

Common Confusions

O(n) is always faster than O(n²)." Not for every small input. Building a hash set has overhead, and a quadratic method can win at small sizes. The growth model tells you what tends to dominate as scale increases; measurements decide close cases.

“Big O gives the exact running time.” It deliberately hides constants, hardware, and implementation details. Use it to compare shapes of growth, then benchmark the real workload when a decision matters.

“The set makes duplicate detection free.” It moves the cost. Memory, hashing, allocation, and assumptions about average-case behavior are now part of the design.

“A correct answer proves the algorithm is good.” Correctness and resource use are separate dimensions. A correct algorithm may still miss a latency budget or exhaust available memory.

Check

A list of 1,000 distinct identifiers requires 499,500 pair comparisons in the model. What happens when the list grows to 2,000 distinct identifiers?

The answer is 1,999,000 comparisons: about four times as many, not twice. That is the signature of the quadratic pair-by-pair shape.

Now ask a second question: which assumption supports calling the set procedure expected O(n)? Each membership check and insertion must be approximately constant time on average. If that assumption is unsuitable, state a different data structure and its cost instead of repeating the slogan.

Practice

Take a familiar task: checking whether a username list contains duplicates, matching repeated product codes, or finding repeated words in a document.

  1. Write the pair-by-pair procedure in plain language.
  2. Identify the repeated work in its no-match case and express it as a sum.
  3. Describe what information a set, map, or sorted structure would retain.
  4. Name one time constraint, one memory constraint, and one assumption that could change your choice.

For an extra challenge, change the requirement from “find any duplicate” to “report every duplicated identifier and its count.” Notice that the set becomes a map from identifier to count. The algorithmic idea remains, but the stored representation changes because the question changed.

Resources

Key Takeaways

PREVIOUS Representation: Bits, Symbols, and Meaning NEXT Abstraction and the Cost of Hiding