Tell us what’s happening:
This is what I understand with the recursion example posted below:

If the argument passed to countup is greater than 1; call the function within itself with (n - 1).

When n == 0, return [ ]

I’m not sure how the integers, 1 - 5, are being appended to the array after the const countArray = countup(n - 1) expression. At that point, n == 0, and 0 is not in the array.

the last function called was countup(0) and this return []

but countup(0) has been called inside the function countup(1)

there is no n++ because there is an n-1 in the function call

so countup(3) is calling countup(2) which is calling countup(1) which is calling countup(0),countup(0) returns [] and now countup(1) can do something with that value, which includes the push method you quoted where n is 1 (as n is the function parameter and 1 the function argument)

When n == 0 (the base case up top), an empty array is returned, not a 0. You never push when n equals 0. The tricky part is that n = (n - 1).

First the function iterates backwards through 5 to 1, calling countup(n) on each value, but not pushing or returning anything until (n - 1) == 0. This is at countup(1 - 1). countup() takes the resolved value of (n - 1) as the actual value of the parameter n. Thus countup(1 - 1) returns an empty array([]), because n == 0. We never call countup(0 - 1).

Next we pop back through through previous function calls, starting with countup(2 - 1), and push the result of 2 - 1 (which is 1) to the array for countArray == [1]. Remember: countup(1-1) has already been resolved, we do not call it a second time. Next:

countup(3 - 1) pushes 2 so countArray == [1, 2], countup(4 - 1) pushes 3 so countArray == [1, 2, 3], and countup(5 - 1) pushes 4 so countArray == [1, 2, 3, 4]. Lastly we pop to the original function call: countArray(5) and push 5 onto the array, thus giving us our final array to return: countArray == [1, 2, 3, 4, 5].