Code

algo find — your first measurement

The last piece is the search itself. Create find.go and find_test.go (see the panel).

LinearFind walks the library from the front and counts every title comparison. That is the whole algorithm: three lines and a counter.

Your first measurement

$ algo gen -n 1000 -seed 42
wrote 1000 items to library.jsonl (seed=42, order=shuffled, dup-rate=0.00)

$ algo find -title "Gubątėva"
found at index 0: Gubątėva — Gąčuku (2020, rating 20)
n = 1000, comparisons = 1

$ algo find -title "Šibąču"
found at index 999: Šibąču — Žąžame (1940, rating 89)
n = 1000, comparisons = 1000

$ algo find -title "Nėra tokio"
not found: "Nėra tokio"
n = 1000, comparisons = 1000

The three cases from step 1, measured: best is 1, worst is 1000, and a miss is 1000 too. A miss is always the worst case — you cannot say something is absent without having looked at everything.

This is what O(n) means

Grow the library tenfold and watch the number:

n=1000     n = 1000,   comparisons = 1000
n=10000    n = 10000,  comparisons = 10000
n=100000   n = 100000, comparisons = 100000

Ten times the data, ten times the work. A straight line. That is exactly what O(n) says, and now you can see it rather than take it on trust.

The test you have to pass

find_test.go checks the answer and the cost:

$ go test -run TestLinearFindCountsAreExact -v .
=== RUN   TestLinearFindCountsAreExact
=== RUN   TestLinearFindCountsAreExact/best_case:_first_item
=== RUN   TestLinearFindCountsAreExact/third_item
=== RUN   TestLinearFindCountsAreExact/worst_case:_last_item
=== RUN   TestLinearFindCountsAreExact/miss:_scans_everything
--- PASS: TestLinearFindCountsAreExact (0.00s)
PASS
ok  	algo	0.417s

Remember this shape — it recurs for the rest of the course. The test demands an exact comparison count, not "roughly that many". In later lessons it will demand "no more than n log n" — and a test like that cannot be fooled by the wrong algorithm, however correct its answers are.

In a real system

So you can see this is not a classroom exercise: transit is a working Vilnius public-transport route planner. It solves the same route two ways — with a naive queue and with a heap — and reports both the time and the operation count. Three different routes:

Query Queue work (naive ÷ heap) Time (naive ÷ heap)
cross-city 249× 16.0×
slow-naive 257× 18.9×
worst case 324× 16.4×

Same query, same answer, and the work ratio is 15–20 times larger than the time ratio. The clock is not lying — it is measuring something other than what you think: the naive queue scans an array front to back, and a processor does that kind of reading very cheaply. A hundred million cheap operations take less time than a million expensive ones.

This is exactly why we count operations in this lesson. If you had measured with a clock alone, you would have found a 16× difference where the difference in work is 249×.

A bound is not a prediction

The same program teaches a second lesson. A naive route search over the full-day graph could in theory do 4.28 × 10¹⁰ operations. Measured, it took 558 ms — only 19.6× slower than the good version, not the ~1000× that had been projected.

The bound did not fail because it was wrong. It failed because a bound is a ceiling, not a prediction: the algorithm stops as soon as it has the answer and never looks at the whole graph.

Keep that distinction — between "the most this could cost" and "what it cost" — for the rest of the course. Big-O tells you the first. The counter tells you the second.