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.