There's been a lot of chatter about (mis)using interrupts recently... often not for the right reasons.
LET'S DISCUSS SOME ARDUINO PROGAMMING FUNDAMENTALS
setup() is executed ONCE after the bootloader, then all program flow is permanently passed to LOOP()
setup() is a convenience of the Arduino environment and doesn't exist in pure C/C++
setup() is intended to 'initialise' the operating parameters for the purposes of the program.
Usually this is all the initialising needed, but there are cases where initialisation can be performed 'locally' to conserve memory etc.
(Read about variable 'scope' and object 'instances')
loop() in Arduino-land is as close to main() is in C/C++. Normally a C/C++ developer would look after the initialisation code, and main-loop() themselves within the main() function.
loop() repeats continuously at the full clock speed of the processor (minus cycles-per-instruction and any barriers (delay etc) imposed by your code). Theoretically, this means it's possible to get close to 4-million* things per pass of loop(). In real terms - you may burn 200 instructions just doing the housekeeping - so 4-million / 200 = 20,000 loop passes per second more-or-less in a real situation.
EXAMPLE: Do you really need to check your pushbutton 20,000 times a second? Probably not.
The solution for this example is much simpler - POLLING
This underscores the reason we all denounce delay() in favour of millis() timing... why waste processor time doing nothing while delay() twiddles its thumbs... ! You'd be surprised how much work an 8-bit 16MHz processor can do!
[[Recall that an old Z80 CP/M computer of the 80's was working with less memory, a quarter of the speed - yet could handle multiple simultaneous database users on serial terminals and printers.]]
POLLING - is the action of 'testing' the input pin(s) each time we go around loop() or any other looping construct - frequently enough to detect the desired events. For a button - that would typically be something less than 10 times a second,
Remember buttons are not only pressed, but they are also released - read about detecting state-change.
This applies equally to all binary (two) state inputs.
NOW - WHAT ABOUT INTERRUPTS
Interrupts are very useful - especially for short-high-frequency events (not buttons#)
An interrupt uses some external (or programmed) event to literally interrupt the current program flow - to capture, mark or perform some other very simple activity.
Program execution returns and continues back where it was interrupted - as soon as the ISR (Interrupt Service Routine) has completed.
HANDLING INTERRUPTS
Short & Sweet. Literally!
The ISR should not spend any more time than absolutely necessary needed to identify what caused the interrupt event, increment or flag 'it' then return. This will keep your program snappy and responsive@, and will not cause any ripple consequence to the main program execution. It MUST be complete before any other interrupt occurs, or you'll start an unenviable journey down the stack & crash road.
Your main() code can check those 'ISR' flags or values to see if changes need to be processed - and perform that at a leisurely pace - as part of the normal code/scheduling within your program.
TAKE-AWAY
Interrupts are useful, but can easily be overused. Keep it simple!
The ISR function should be as short and to-the-point as possible, to reduce latency and other unwanted consequences.
Process your fast events at a leisurely pace, Rarely do you need to act on every fast input event soon as it comes in... you can accumulate or post-process when you have time in the main code.
(If you do, you're probably using the wrong processor, or need to really rethink your code architecture.)
Footnotes
* on a 16MHz 8-bit Atmel AVR
# interrupt handling of low-speed events is an option, but for specific reasons it is unlikely to be found in 90% of Arduino applications due to the side-effects / overhead of managing the interrupts.
@ consequences of overflowing interrupts and performance impacts are particularly relevant on low-speed processors with limited stack space - as each interrupt event uses the stack, and if they overlap - you will eventually 'run out' of stack space!