What belongs inside an interrupt service routine, and what must be kept out of it?
Interrupt service routine rules are simple: acknowledge the hardware, capture the minimum safe state and return fast. Blocking calls, allocation, logging and long loops belong in deferred work because ISR runtime becomes worst-case latency for interrupts at the same or lower priority.
5 min readUpdated 2026-08-15
0
Answer
Short answer
Keep an ISR tiny: acknowledge the device, copy the minimum data, schedule deferred work, and avoid blocking, allocation, logging or loops that stretch interrupt latency.
Keep interrupts explicit in the answer because that is the concept the interviewer is actually trying to test.
Was this answer helpful?
Likely follow-ups
→Your handler updates a 64-bit microsecond counter that the main loop reads. How do you make that read safe without disabling interrupts?
→Why can clearing a peripheral's interrupt flag on the last line of the handler cause the handler to run twice?
→How do you measure the real worst-case execution time of a handler on hardware rather than reasoning about it?
→When is it correct to do the whole job inside the ISR and defer nothing?
An interrupt handler runs with other interrupts masked, or at least with everything at or below its own priority masked. So the time it spends executing is not merely its own cost. It is added directly to the worst-case interrupt latency of every source that cannot preempt it. A 200-microsecond handler on a timer is not a slow timer handler; it is a 200-microsecond hole in the responsiveness of the UART, the ADC and the motor commutation interrupt, and the symptom appears there rather than where the fault is.
That reframing is the answer to "why keep it short". The rule is not stylistic hygiene, it is that you cannot reason about any deadline in the system without knowing the sum of the handler times that can run ahead of it.
So the handler does three things: it acknowledges the hardware so the source stops asserting, it captures whatever is perishable such as a received byte or a capture-register timestamp, and it signals that work is pending. Then it returns.
What must not be in there
Anything that can block, because there is nothing to block on. An ISR is not a task; it has no context to suspend. Taking a mutex that a task holds cannot work, since the task cannot run to release it while the handler occupies the core, and the result is a hard hang rather than a wait. RTOSes address this by providing a separate set of ISR-safe primitives — typically the same queue and semaphore operations with a distinct entry point that never blocks and only signals — and calling the task-context version from a handler is one of the most common bugs in a first RTOS port.
Dynamic allocation is out, because the allocator's data structures are shared with whatever the handler interrupted and the allocator is generally not reentrant. Formatted printing is out for the same reason plus its cost: a printf to a polled UART can take milliseconds. Busy-waiting on a peripheral is out, because you have converted a bounded handler into one whose duration depends on an external device.
The general test is reentrancy. Any function called from the handler must be safe to enter while another invocation of it is part-way through, which rules out anything holding internal static state — string tokenisers, error-code globals, non-reentrant library maths routines that keep scratch state.
Passing state out without tearing it
The handler and the main loop share memory, and nothing serialises them. Two separate mistakes hide here, and a strong answer separates them.
The first is the compiler. If a variable is written by the handler and read in a loop by main, the compiler is entitled to hoist that read out of the loop, because within the visible control flow nothing changes it. volatile fixes exactly this: it forces the access to happen at the point the source says, every time. That is all it does.
The second is atomicity, and volatile does nothing for it. On a 32-bit core a naturally aligned 32-bit load is a single instruction and is safe. A 64-bit counter is not, and neither is a struct, nor an aligned 32-bit value on an 8- or 16-bit core. If the interrupt lands between the two halves of a read, the main loop gets a value composed of the old high word and the new low word — a number that was never written.
The alternative is to mask interrupts around the read, which is correct and costs you latency on every source for the duration. Either answer is acceptable; not knowing that a choice exists is not. Where the language offers it, C11's _Atomic types express the intent directly and let the compiler pick the mechanism, though on many small targets the implementation reduces to masking anyway.
The split, drawn
The deferred half is what makes the short handler useful, and its shape decides whether data is ever lost.
Loading diagram…
Look at the gap between the two halves rather than at either half. The ring buffer must be sized for the longest the task can go without draining it, which means the handler's correctness depends on the scheduling of code that is not in the handler. Overflow here is silent unless you count it, so a production ring buffer increments a dropped-byte counter that something eventually reports.
The flag-clear race that makes a handler run twice
Worth knowing because it looks like a hardware fault. Clearing a peripheral's interrupt flag as the very last statement of the handler is a natural way to write it, and on cores with a write buffer between the CPU and the peripheral bus the write can still be in flight when the handler returns. The interrupt controller sees the source still asserted and re-enters the handler immediately, so the byte is processed twice or a spurious edge is counted.
The fix is to clear the flag early and then force the write to complete before returning — commonly by reading the register back, or by issuing a data synchronisation barrier. Clearing early has a second benefit: an event arriving during the handler is then correctly latched as a new pending interrupt rather than being lost.
The interview signal is not the list of forbidden calls. It is whether you can say, for one specific handler, how long it runs, whose deadlines that time is charged against, and what happens to the data if the deferred half is late.