Interview questions & answers
Q1. What is an RTOS and how does it differ from a general-purpose OS?
An RTOS guarantees that a task completes within a bounded, deterministic time window, whereas a general-purpose OS like Linux optimizes for average throughput rather than worst-case latency. FreeRTOS on an STM32F4, for example, can respond to a GPIO interrupt within 1–2 microseconds deterministically. The key metric is not speed but predictability — a missed deadline in an airbag controller is a failure even if the system is otherwise fast.
Follow-up: What is the difference between hard real-time and soft real-time systems?
Q2. Explain the difference between preemptive and cooperative scheduling in an RTOS.
In preemptive scheduling the kernel can interrupt a running task at any tick to run a higher-priority task, while in cooperative scheduling a task runs until it voluntarily yields. FreeRTOS uses preemptive scheduling by default with a 1 ms tick, so a priority-5 task immediately preempts a priority-3 task the moment it becomes ready. Cooperative scheduling avoids stack corruption bugs but causes priority inversion if any task has a long non-yielding loop.
Follow-up: How does the RTOS know when to switch tasks in preemptive mode?
Q3. What is a semaphore and how is it different from a mutex in FreeRTOS?
A semaphore is a signaling primitive with a count, while a mutex is a semaphore with ownership and priority inheritance built in. In FreeRTOS, xSemaphoreCreateMutex() creates a mutex that automatically boosts the holding task's priority to prevent priority inversion, whereas a binary semaphore created with xSemaphoreCreateBinary() has no such protection. Using a binary semaphore for mutual exclusion instead of a mutex is a common source of priority inversion bugs in production firmware.
Follow-up: What is priority inversion and how does a mutex prevent it?
Q4. What is priority inversion and can you give a real embedded example?
Priority inversion occurs when a high-priority task is blocked waiting for a resource held by a low-priority task, while a medium-priority task runs freely and starves the high-priority one. The Mars Pathfinder mission in 1997 experienced exactly this — a low-priority meteorological task held a shared bus mutex, a medium-priority communications task kept running, and the high-priority data bus task starved until the watchdog reset the spacecraft. Priority inheritance in mutexes temporarily elevates the low-priority holder's priority to unblock the system.
Follow-up: How does priority ceiling differ from priority inheritance?
Q5. What is a task stack and how do you decide its size in FreeRTOS?
Each FreeRTOS task has its own stack allocated from heap at creation; the size is passed as words to xTaskCreate() and must cover the deepest call chain plus local variables plus interrupt nesting depth. In practice, for an STM32 task that calls printf through a UART HAL driver, 512 words (2 KB) is a typical starting point, verified with uxTaskGetStackHighWaterMark() after stress testing. Under-sizing the stack is one of the most frequent causes of silent data corruption in RTOS firmware because the stack overflow often corrupts an adjacent task's TCB.
Follow-up: How does FreeRTOS detect a stack overflow at runtime?
Q6. What is a message queue in an RTOS and when would you use it over a global variable?
A message queue is a kernel-managed FIFO buffer that safely transfers data between tasks without shared-memory race conditions. In FreeRTOS, xQueueCreate(10, sizeof(SensorData_t)) creates a queue of 10 sensor structs; the ADC task sends structs and the processing task blocks on xQueueReceive(), eliminating polling. A global variable shared between tasks requires a mutex and careful access ordering, while a queue provides both data transfer and synchronization in one primitive.
Follow-up: What happens if a task tries to send to a full queue with a zero timeout?
Q7. What is the FreeRTOS tick interrupt and why is its period important?
The tick interrupt is a periodic timer ISR — typically SysTick on ARM Cortex-M — that drives the RTOS scheduler, increments the tick count, and unblocks tasks whose delay has expired. At the default 1 ms tick period, vTaskDelay(100) suspends a task for exactly 100 ms. Choosing too short a tick period (say, 100 µs) wastes CPU in scheduler overhead, while too long a period (10 ms) degrades timing resolution for tasks that need sub-millisecond delays.
Follow-up: How does FreeRTOS achieve tickless idle mode and why is it used?
Q8. What is a watchdog timer and how do you use it correctly in an RTOS?
A watchdog timer is a hardware counter that resets the MCU if not refreshed within a set period, providing a last-resort recovery from software hangs. In an RTOS system with multiple tasks, the correct pattern is to have each task set a bit flag in a shared word, and a dedicated low-priority watchdog task that only kicks the IWDG on STM32 after confirming all flags are set. Kicking the watchdog from a single high-priority task is wrong because it will happily reset even if all other tasks are deadlocked.
Follow-up: What is the difference between an independent watchdog and a window watchdog?
Q9. What is deadlock in an RTOS context and how do you prevent it?
Deadlock occurs when two or more tasks each hold a resource the other needs, causing all to block forever with no task able to proceed. For example, Task A holds Mutex1 and waits for Mutex2 while Task B holds Mutex2 and waits for Mutex1 — both block permanently. Prevention strategies include always acquiring mutexes in a fixed global order across all tasks, using timeout-based xSemaphoreTake() calls, and minimizing the number of simultaneously held mutexes.
Follow-up: How would you detect a deadlock at runtime in an embedded system?
Q10. What is the difference between vTaskDelay and vTaskDelayUntil?
vTaskDelay suspends the calling task for N ticks relative to when the call is made, so cumulative drift builds up over time; vTaskDelayUntil suspends until an absolute tick count, making the loop period exact regardless of execution time. A sensor sampling task that must sample at exactly 100 Hz must use vTaskDelayUntil(previousWakeTime, 10) not vTaskDelay(10), otherwise the execution time of the task body itself shifts the sampling phase each iteration. This distinction matters in any control system where consistent sample intervals affect filter or PID performance.
Follow-up: What happens if the task body takes longer than the period in vTaskDelayUntil?
Q11. What is a binary semaphore and how is it used for ISR-to-task synchronization?
A binary semaphore is a kernel object that can hold a count of 0 or 1 and is the standard mechanism to signal a task from an ISR without doing work inside the ISR. When a UART receive-complete interrupt fires on an STM32, the ISR calls xSemaphoreGiveFromISR() which unblocks the UART processing task; the task then reads and parses the buffer outside interrupt context. This deferred interrupt processing pattern keeps ISRs short and deterministic while allowing complex processing at task level.
Follow-up: Why can't you use xSemaphoreGive() directly inside an ISR?
Q12. How does FreeRTOS manage memory — what heap schemes are available?
FreeRTOS provides five heap implementations (heap_1 through heap_5): heap_1 only allocates and never frees, heap_2 allows freeing but can fragment, heap_3 wraps standard malloc with scheduler suspension, heap_4 uses first-fit with coalescence and is the most common choice, and heap_5 allows spanning multiple non-contiguous memory regions. For a Cortex-M4 with separate SRAM1 (128 KB) and SRAM2 (32 KB), heap_5 lets you assign tasks to SRAM1 and ISR stacks to SRAM2 for better memory utilization. Dynamic allocation in ISRs is always forbidden because heap functions are not ISR-safe.
Follow-up: What is the risk of using heap_2 in a long-running embedded system?
Q13. What is an event group in FreeRTOS and when is it preferred over multiple semaphores?
An event group is a set of 24 binary flags in a single kernel object, and a task can block waiting for any combination of flags to be set simultaneously, which is impossible with individual semaphores. In a motor controller, an event group with bits for ENCODER_READY, CAN_MSG_RECEIVED, and SAFETY_OK allows the control loop task to start only when all three are set using xEventGroupWaitBits() with the wait-for-all option. This eliminates a cascade of three semaphore pends and the race conditions that come from checking them sequentially.
Follow-up: Are event group bits set atomically in FreeRTOS?
Q14. What is context switching and what does the RTOS save and restore during it?
Context switching is the act of saving the CPU state of the running task and restoring the CPU state of the next task to run, making each task appear to have its own private CPU. On ARM Cortex-M4, the hardware automatically pushes R0–R3, R12, LR, PC, xPSR (and optionally FPU registers) onto the task stack on exception entry; FreeRTOS's PendSV handler then saves R4–R11 manually and switches the PSP to the new task's stack. If the FPU is used but the port is not configured with configUSE_TASK_FPU_SUPPORT, FPU register corruption is silent and extremely hard to debug.
Follow-up: Why is PendSV chosen as the context-switch interrupt on Cortex-M?
Q15. What is the idle task in FreeRTOS and what should you never do in idle hook?
The idle task runs at priority 0 whenever no application task is ready; FreeRTOS creates it automatically and uses it to free memory of deleted tasks when heap_4 is used. The idle hook (vApplicationIdleHook) is called each idle cycle and is a valid place to put the MCU into sleep mode, but you must never block or call any API that could block — doing so starves the idle task and prevents deleted task memory from being reclaimed. In a battery-powered IoT device, calling __WFI() in the idle hook drops a STM32L4 from 10 mA active to under 10 µA during idle periods.
Follow-up: What happens if a deleted task's memory is never reclaimed on heap_1?
Common misconceptions
Misconception: A mutex and a binary semaphore are interchangeable for protecting shared resources.
Correct: A mutex has ownership and priority inheritance so only the task that took it can give it, while a binary semaphore has no ownership and causes priority inversion if used for mutual exclusion.
Misconception: vTaskDelay(100) always waits exactly 100 ms regardless of tick rate.
Correct: vTaskDelay(100) waits 100 ticks, so the actual time depends on configTICK_RATE_HZ — at 100 Hz it is 1 second, not 1 millisecond.
Misconception: Higher priority numbers in FreeRTOS mean higher task priority.
Correct: In FreeRTOS, priority 0 is the lowest (idle) and higher numbers mean higher priority, but this is opposite to some other RTOS like VxWorks where 0 is highest.
Misconception: Calling vTaskDelay inside an ISR is safe as long as the delay is very short.
Correct: No RTOS blocking call including vTaskDelay can ever be called from an ISR; only the FromISR variants of API functions are ISR-safe.