endNum itself is not being reduced by 1, the value of the second argument to rangeOfNumbers() is being reduced by 1. The first time you call this function you hit the else block. If we show the actual values being used it looks like:

var numbers = rangeOfNumbers(1, 4);
numbers.push(5);

Passing endNum - 1 to rangeOfNumbers does not reduce the value of endNum by 1, thus we are still pushing the number 5 onto numbers.

The key here is that numbers.push(endNum) is not called until after the recursive call to rangeOfNumbers. In the original function call we hit the else block and the first line evaluates to:

var numbers = rangeOfNumbers(1, 4);

We have a recursive function call, so we don’t go any further in the else block, we have to evaluate this function call first. So we call it for a second time (with the args above) and we hit the else block again and the first line evaluates to:

var numbers = rangeOfNumbers(1, 3);

Again, we can’t go any further than this because we have to call rangeOfNumbers again. Each time we make the recursive call we are stopping the current function call from proceeding to the push because we have to wait for the recursive call to return a value. We keep doing this until we hit the base case rangeOfNumbers(1, 1) at which point we can finally return something. For the base case here we would return [1].

Now we can unravel the recursion. The recursive function call before the base case is waiting at:

var numbers = rangeOfNumbers(1,1);

Since we have a value for the base case we can rewrite it as:

var numbers = [1];

And now we can execute the push. Remember that the recursive call rangeOfNumbers(1,1) is passing in endNum - 1 as the second argument, but endNum still equals 2 in the function that is waiting for the base case to return, so the push becomes:

numbers.push([2]);

And we know that numbers = [1] so we can rewrite that as:

[1].push([2])

which is:

[1,2]

And this is the value we return to the recursive function call that is waiting one level higher in the call hierarchy. Keep repeating this process until we get back to the original function call and we have: