You must have heard that phrase before. No matter what you do, that new thing needs your attention, now! Whether it is a rogue bus heading your way or deflector shields giving in, you will have to interrupt yourself to steer out of harm's way or to give her all she's got [StarTrek quote].
Joke aside, same principle applies for processors and controllers. At any given moment a program can be interrupted to execute a predefined routine. The interrupt is triggered by a factor that's external to the processor itself. Both Snake Game and Digital Oscilloscope setups used buttons on analog inputs. It was a reasonable start solution and a good lesson but far from efficient. This is one place where interrupts come handy.
The controller executes a series of instructions that form a program. After a small and fix number of instructions, the controller checks the state of the interrupt pin. Should a change occur, it suspends the execution of the program to run the interrupt routine. It then resumes the program once the routine has ended.
Unlike checking the state of an input in a loop, known as pooling, the processor does that a lot quicker. This fast reaction time makes interrupts suitable for reading input from external devices whose outputs may be omitted otherwise. In the Snake Game, program's main loop starts by pooling buttons state based on which decisions are made later in that iteration. If a button is pressed for a brief moment and then released, the program may miss that, failing to turn the snake head in time to avoid collision.
An interrupt can be configured to get triggered when the pin gets HIGH or LOW from the assumingly stable corresponding opposite state. It can use the rising or falling edge of the input for that same purpose.
For some controllers, interrupts are a limited resource. The ATMEGA 328 for instance, the most popular among Arduino boards, has only two inputs with such capability. Luckily, similar inputs can be multiplexed with little to no effort.
LOW to trigger interrupt - input multiplexingIn the schematics above, the three inputs are kept HIGH by their pull-up resistors R1-3. The interrupt pin is also stable in HIGH state thanks to R4. When a button is pressed, say SW3, the corresponding pin Conn5 is pulled down to GND creating a path for a current to flow through diode D3. Depending on diode ratings, the junction voltage will be 0.5 - 1.0 volts from GND, enough to appear as a LOW to the interrupt pin. The main function of the diodes is to prevent pulling down all the inputs that share the same interrupt, once a button is pressed.
ISRs, or Interrupt Service Routines, should not contain heavy processing that could keep the controller or the processor from normal execution. Some programs contain critical sections that do not go well with interrupts. Once such example is the Digital Oscilloscope's acquisition state. If an interrupt were to stop the ADC reading loop some samples will be lost, thus compromising the data. For such a time-sensitive case, the interrupts can be suspended before entering the sensitive code and reinstated after. The Arduino noInterrupts() and interrupts() do just that.
So, if you're looking for quick reaction to outside factors for your program, the interrupts are the goto solution. Knowing that an ISR is injected into a program's normal execution, the external event probability needs to be contained and the routine kept as simple as possible but sometimes, to be on the safe side, they just need to be completely suspended.