The call stack steps that produce the result:
1. n = 5 => [5, ...countdown(4)]
2. n = 4 => [5, 4, ...countdown(3)]]
3. n = 3 => [5, 4, 3, ...countdown(2)]]
4. n = 2 => [5, 4, 3, 2, ...countdown(1)]
5. n = 1 => [5, 4, 3, 2, 1, ...countdown(0)]
6. n = 0 => [5, 4, 3, 2, 1, ...[]]
So the ... is spreading syntax notation that works like concat for two arrays.
Another schema could be like:
1. n = 5 => [5, ...countdown(4)]
2. n = 4 => [5, ...[4, ...countdown(3)]]
3. n = 3 => [5, ...[4, ...[3, ...countdown(2)]]]
4. n = 2 => [5, ...[4, ...[3, ..[2, ...countdown(1)]]]]
5. n = 1 => [5, ...[4, ...[3, ...[2, ...[1, ...countdown(0)]]]]]
6. n = 0 => [5, ...[4, ...[3, ...[2, ...[1, ...[]]]]]]
Think about that like function call stack from left to right:
x1( x2( x3() ) ) => ...( ...( ...(n) ) )
countdown(20000)vsArray.from({ length: 20000 }, (_, i) => 20000 - i), but i guess its purpose is to showcase ES6 features?