So you have 459 bytes available.
When setup() is executed, it's called from a function called main() (hidden for you). To know where to continue after setup is finished, the return address is placed on the so-called stack; on a 328P that will cost 2 bytes so you now have 457 bytes left; they will become available again once setup() is finished.
The same happens when loop() is executed, so you keep having either 459 bytes available or 457 bytes available.
So now in loop() you have e.g. a local counter; when loop() is executed, that local counter 'lives' on the stack and assuming it's an int that costs 2 bytes (so down to 455 bytes). If you have a local array of e.g. 10 floats declared in loop(), that will cost 40 bytes.
Next in loop() you call Serial.println() to print that counter; the return address where to continue after the call needs to be remembered so after your print statement the next statement can be executed. Another 2 bytes down the drain so you now have 453 bytes left. Once the Serial.println() returns, you have 455 bytes left.
Now lets assume that an interrupt occurs while the Serial.println() is being executed. For the program to know where to continue after the interrupt handler is finished the return address is placed on the stack when the interrupt happens; again 2 bytes down. So 453 - 2 equals 451 bytes left. Further the interrupt handler will save a number of registers on the stack (not sure how many, let's say 10), so now you only have 441 bytes left.
Once the interrupt is handled the saved bytes are restored, the interrupt handler returns to where it interrupted the normal program flow and the 2 plus 10 bytes are available again. So back to 453 bytes, when Serial.println() ends back to 455 and when loop() ends the two bytes allocated to the counter are available again (so 457 bytes) and once returned to main() the return address is available again and you have the original 459 bytes.
So now let's assume that you wrote you own function that is called from loop() and calls digitalWrite(). The same story, you will loose 2 extra bytes in this case.
So that is one part.
The next 'problem' can be dynamic memory allocation. The IDE does not report on that. If you use e.g. the Adafruit Neopixel library with 100 NeoPixels, that will cost 300 bytes (which are allocated on the heap) so you are down to 159 bytes before even getting to loop(); those 300 bytes will permanently live.
Note that heap and stack grow towards each other; your problems come when they collide and placing something on the stack overwrites a variable on the heap or you modify a variable that is on the heap but because the e.g. the return address is now there you corrupt the return address and once the functionality that placed that return address on the stack ends (function cal, interrupt) it does not continue where it is supposed to continue.
And lastly it depends of optimisations that the compiler applies to your code. You might not find a call to setup() and/or loop() in the executable file that you upload but it can be a jump so the 2 bytes that were lost with a call are not lost as there is no need to return to a specific address.
Life is complicated.
Something to study: