Skip to content
Tech Interview Prep home
Technical interview guide

Interrupts & Interrupt Service Routines

How hardware interrupts preempt normal execution, and the discipline required to write an ISR that doesn't corrupt shared state or blow the interrupt budget.

Read
55 min
Practice MCQs
25
Interview QA
25
Edition
v3
Editorial status
Reviewed

Scope: CMSIS-Core 6.2 interrupt APIs; current FreeRTOS Cortex-M port guidance; current GCC interrupt attributes and Arm ABI reviewed 2026-09-04.

Overview

Curated: · Written: · Reviewed:

An interrupt is an asynchronous transfer of control from foreground work to an exception handler selected through an architecture and device-specific vector. Correctness spans the interrupt source, peripheral status and mask registers, interrupt controller enable/pending/active state, priority and preemption rules, vector table, compiler-generated exception entry, stack, and the code that acknowledges the source. Enabling a controller line does not configure the peripheral, and clearing controller pending state does not necessarily acknowledge a level-sensitive device source.

An interrupt service routine (ISR) must have a bounded worst-case execution time, use the target's required declaration and ABI, and perform only operations documented as interrupt-safe. A common design captures a timestamp/status snapshot, acknowledges the exact source with correct register semantics, transfers bounded data or an event to foreground/RTOS work, and exits. Blocking locks, waits, dynamic allocation, unbounded loops, heavy formatting, and ordinary task APIs are usually invalid in interrupt context. Volatile does not make ISR-shared data atomic or establish a complete synchronization protocol.

Priority is a system resource rather than a local preference. On Cortex-M, numeric priority zero is the highest urgency; implemented priority bits, grouping, masking registers, RTOS syscall ceilings, nesting, tail chaining, and FPU or security context all affect latency and stack consumption. Firmware must measure interrupt response, execution, masking windows, backlog, loss, and nesting using the release image under adversarial event rates.

The production invariant is bounded interrupt-to-service fidelity: every accepted hardware event is serviced, intentionally coalesced, or observably rejected within a declared latency and capacity bound, without corrupting shared state or starving higher-level work. Retained evidence must distinguish source assertion, controller delivery, handler entry, acknowledgement, deferral, completion, overflow, and recovery.

Latency is a measured path, not a priority number. On a 168 MHz Cortex-M4 with 4-cycle flash wait states, the hardware exception entry is 12 cycles (71 ns) if the stack is in SRAM, plus FPU lazy stacking of 16 extra words if the interrupted context used the FPU — another 16 × 2 wait-stated stores, about 190 ns more, and 64 bytes of stack. An ISR that formats a log line with snprintf at 8 µs while a 50 µs control loop runs at priority 5 below it will miss the loop whenever the log fires; 8 µs is 16% of the period and is already the budget. Measure with a GPIO toggled at ISR entry and exit on the release image: if the high-water mark is 11.4 µs against a 12 µs budget, the next feature does not belong in that ISR.

Level-sensitive sources re-enter until the peripheral is cleared. Clearing NVIC pending while the UART RXNE bit stays set produces an interrupt storm at the core clock: 168,000 entries per millisecond, each 12 cycles, which is 100% CPU and a watchdog reset in 80 ms. Edge-triggered lines miss a second edge that arrives while the handler is active unless the handler re-reads the pin or the peripheral FIFO. Shared vectors (EXTI9_5 on STM32 covers five pins) require a documented scan order and a bound: if pin 7 and pin 9 assert together, both flags must be handled in one entry or the second waits for the next edge that may never come. BASEPRI masking to 0x40 to protect a 20-cycle RMW is not CPSID; it still admits priority-0 faults and NMI, which is the point, but it also admits any interrupt numerically more urgent than 0x40, so a 1 µs bit-bang that assumed “all IRQs off” is not protected. Paint the protocol: which data, which barrier, which mask, which ISR, on the release binary, under a burst generator.Tail-chaining on Cortex-M skips stacking when a pending IRQ preempts at the same priority grouping; it saves 6 cycles (36 ns at 168 MHz) but the second handler sees the first's remaining stack frame. That is fine until the first handler used extra callee-saved registers the compiler allocated only for that function; do not write ISRs that assume a fresh frame beyond the architecture's stacking. FPU lazy stacking defers S0–S15 until the ISR uses float; an ISR that accidentally calls a float helper then pays 16 words. Compile ISRs with -mgeneral-regs-only or -mfpu=none for that file.

Missed edges on EXTI configured as rising-only drop a 2 µs glitch that a 48 MHz sample of the GPIO IDR would have caught if the handler re-read the pin. For pulses narrower than the ISR entry, use a hardware latch or input capture, not EXTI alone. Nested same-IRQ on a level source that is not cleared is the storm already priced; nested different IRQ at higher priority needs stack: each nesting 8 words of hardware frame plus callee save. Four nestings is 128+ bytes; the MSP budget must include it. Generate bursts with a timer at 1.2× the specified max rate and record the overflow counter, not a single clean interrupt in a demo.

Interrupt verification is a release-image ritual that starts at a GPIO bit and ends at a burst generator. Record SYSCLK, wait states, compiler flags, .map sizes, painted stack high-water marks, ISR GPIO timing, logic-analyzer traces of CS/SCK/SDA, current-shunt waveforms at not less than 100 kHz, reset-cause and fault registers, and the boot slot/security counter after every power-loss injection. A pass is a number that can be recomputed from those artifacts: flash LOAD versus FLASH LENGTH, ISR high-water versus period, Stop current versus the schematic budget, confirm window versus the health checks, and disable-to-deny for debug and keys. If the only evidence is a green LED, a UART log, or a debugger session on an -O0 build, the claim is unpublished. Repeat the same measurements at the temperature and voltage corners the datasheet allows, because flash wait states, Stop leakage, crystal error, and brownout thresholds all move, and a 25 °C passing suite is not a 85 °C passing suite.

Worked example: clear NVIC pending, leave UART RXNE set

168 MHz Cortex-M4. Level-sensitive UART RX. Handler clears NVIC pending only.

ackentries per msCPUwatchdog
NVIC pending only, RXNE still set168,000100%reset at 80 ms
read DR (clears RXNE), then NVICone per byteboundedstays up

A GPIO toggle on the release image is the latency budget. Clearing the controller is not acknowledging the source.