Run it
The call count for plain recursion, which is the argument for everything that follows.
The next block continues with the remaining demonstrations.
The three fixes, agreeing, with the space each one uses.
The next block continues with the remaining demonstrations.
The next block continues with the remaining demonstrations.
And the reason bottom-up is not merely a preference.
The next block continues with the remaining demonstrations.
Where the recurrence comes from
Work backwards from the destination rather than forwards from the start. To be standing on step n, the previous step was either n−1 (and you took one step) or n−2 (and you took two). Those two sets of routes are disjoint and together they are all of them, so the counts add.
That is the whole derivation, and it is worth saying in exactly that form: every route ends with one of two moves, so partition by the last move. The same sentence derives the recurrence for coin change, for house robber, and for most one-dimensional DP.
The base cases are where people slip. ways(1) = 1 and ways(2) = 2 — and if you define ways(0) = 1 (one way to stand still) the recurrence works from n = 2 and the sequence lines up with standard Fibonacci.
Why plain recursion is exponential
ways(5) calls ways(4) and ways(3); ways(4) calls ways(3) again. The recursion tree has two branches at almost every node and a depth of n, so the number of calls grows like the golden ratio to the n — and the editor below prints it: 109 calls at n = 10, 13,529 at n = 20, over 150,000 at n = 25.
This is overlapping subproblems, and it is one of the two conditions that make a problem a DP problem. The other is optimal substructure: the answer for n is built from the answers for smaller n, unchanged by how you got there. Naming both is a good way to show you know why the method applies rather than pattern-matching to it.
Four versions, in the order to present them
Recursion — states the recurrence clearly and is unusable. Write it, say it is exponential, move on.
Memoised recursion — @functools.lru_cache on the same function. One line, O(n) time, O(n) space, and it keeps the recursive shape that made the recurrence obvious. This is top-down.
A forwards table — bottom-up, no recursion, no stack depth limit. Same complexity, and it makes the next step visible.
Two variables — because the table is only ever read two entries back. O(1) space, and the version to end on. Offering all four in that order, briefly, is worth more than jumping straight to the last one: the interviewer wants the reasoning, and the reasoning is the ladder.
The follow-ups
"What if you can climb 1, 2 or 3 steps?" The recurrence gains a term and the rolling window becomes three variables. The general version — any set of allowed step sizes — is coin change counting combinations, which is the next question.
"Can it be faster than O(n)?" Yes, and this is the answer that surprises people: Fibonacci has a closed form, and matrix exponentiation computes it in O(log n) multiplications. Worth naming; not worth writing unless asked, because the numbers get big enough that the multiplications stop being O(1).
Edge cases to raise
Volunteering these is most of what separates a correct answer from a good one.
n = 0 and n = 1. Decide whether standing still counts as one way. Defining ways(0) = 1 makes the recurrence work from n = 2 with no special cases.
Large n. The numbers get big - rolling(4000) has 836 digits. Python handles it; a language with fixed-width integers overflows silently.
Recursion depth. The memoised version dies around n = 1000 by default. That is the practical argument for bottom-up, not a stylistic one.