When a inner function is maintaining a reference to a variable of an outer function in JavaScript a clousure is formed, that is the members of the outer function is not destroyed when the outer function is done running since it's variables are still being referred from another function.
This is because when a function is executed a new Execution Context is created for that function's environment. It maintains a scope chain and is then pushed to the execution stack in the order of execution.
There is a global Execution context, then the invocation of the countNumbers
creates its own execution context and finally the execution context of the callback is created. The callback function execution context maintains a pointer to the scope of the countNumbers
function's execution context which maintains a pointer to the execution context of the global:

In your case, when the setInterval()
runs it immediately/synchronously returns the ID and it is set in the timer
variable. Since the timer
variable is defined in the scope of the countNumbers
function the inner/callback function forms a clousure over it. The execution context of the callback can reach and access the timer
variable through the scope chain.
Now when you run the setInterval
the callback is queued and executed at the specified interval asynchronously. Due to the clousure formed earlier the callback has access to the timer
variable declared in the outer function scope, so when the number
reaches 10 it can use that value of the ID from the outer function scope to cancel the interval.