Theory

The guarantee, up to a million

In step 1 you wrote down a guess: what will the height be at a million records, with strictly ascending input?

$ go test -run AVLHeightGrowth -v

         n   height     log2 n    h/log2n    cmps/lookup   rot/insert
      1000       10       10.0       1.00          8.987        0.990
     10000       14       13.3       1.05         12.363        0.999
    100000       17       16.6       1.02         15.689        1.000
   1000000       20       19.9       1.00         18.951        1.000

20.

A million records, the worst possible insertion order, and any title is found in 19 comparisons.

The h/log2n column

1.00   1.05   1.02   1.00

Not "roughly logarithmic". The height is log2(n), rounded.

Compare with the same column in lesson 11, where the BST was being fed shuffled — favourable — input:

h ÷ log2 n
BST, shuffled input (lesson 11) 1.97 – 2.47
AVL, sorted input (lesson 12) 1.00 – 1.05

The AVL tree at its worst is about half as deep as the BST at its realistic best.

And it holds for any order

Lesson 11's homework asked for a third worst-case order — neither ascending nor descending. The answer was a zigzag: take alternately from the two ends of what remains. It also produced height n.

n = 20000, bound 20.3

ascending    height 15    0.999 rotations per insert
descending   height 15    0.999 rotations per insert
zigzag       height 18    1.623 rotations per insert

The zigzag is the most expensive of the three: 1.62 rotations per insertion instead of 1.0, and a tree three levels taller. And still comfortably under the bound.

That is what the word "guarantee" means. Not "it worked for the inputs we tried" but "there is no input for which it fails" — because the bound follows from an invariant that every node checks for itself.

What changed since lesson 11

Lesson 11's step 8 listed four requirements. Put them beside what you have:

requirement met?
the tree rearranges itself during insertion yes — rebalance on the way back up
the rearrangement costs O(log n), not O(n) yes — one check per node on the path
the BST invariant survives yes — TestRotationsPreserveTheOrdering
the height stays O(log n) guaranteed yes — the bound, checked by a test

Which means lesson 11's test now has to fail. It does: TestSortedInsertDegeneratesIntoAList requires Height() == n, and this tree gives 14. Breaking that test was the point of this lesson.