Table of Contents
KEY TAKEAWAYS
- Embedded interviews test both C programming depth and hardware understanding — expect questions on volatile, pointers, bit manipulation, and memory layout.
- Whiteboard coding typically involves implementing ring buffers, state machines, debouncing algorithms, or driver functions.
- Protocol questions (I2C, SPI, UART) focus on timing, error handling, and real debugging scenarios rather than textbook definitions.
- RTOS questions test understanding of priority inversion, deadlock prevention, and interrupt-safe queue implementations.
- Debugging scenarios are common: “The system crashes randomly” — walk through your systematic debugging approach.
C Programming Questions
These are the most common C questions in embedded interviews. They test whether you truly understand the language at the level needed for firmware development.
Q1: What does volatile do and when do you use it?
This is the single most common embedded interview question. Interviewers ask it because it immediately reveals whether a candidate understands the hardware-software boundary. A candidate who cannot explain volatile likely has not written register-level code. Here is a thorough answer that covers all three use cases.
/* The 'volatile' keyword tells the compiler that a variable's
* value can change at any time, outside the normal program flow.
* The compiler must NOT optimize away reads or writes to it.
*
* Three mandatory uses in embedded C:
*/
/* 1. Hardware registers — value changes by hardware */
volatile uint32_t *GPIO_IDR = (volatile uint32_t *)0x40010808;
/* Without volatile, the compiler might read GPIO_IDR once,
* cache the value, and never re-read the register:
* while (*GPIO_IDR & 0x01); // Compiler thinks: "\never changes, infinite loop"
* // Optimizes to: while(1);
*/
/* 2. Variables modified by ISRs */
volatile uint8_t button_pressed = 0;
void EXTI0_IRQHandler(void) {
button_pressed = 1; /* Modified in ISR */
}
void main_loop(void) {
while (!button_pressed); /* Without volatile, compiler optimizes this away */
handle_button();
}
/* 3. Variables shared between threads (RTOS tasks) */
volatile uint32_t shared_counter = 0; /* Accessed by multiple tasks */
/* Key point: volatile does NOT make operations atomic!
* shared_counter++ is still a read-modify-write sequence.
* You still need mutexes or critical sections for thread safety. */Q2: Explain the difference between const and #define
This question tests your understanding of the C preprocessor versus the compiler. Both are used for constants, but they work at fundamentally different stages of compilation and have different implications for type safety, debugging, and scope. A strong answer demonstrates practical awareness of when each is appropriate in embedded code.
/* #define is a preprocessor text replacement — no type, no scope */
#define MAX_SIZE 256
/* Becomes the literal number 256 everywhere. No type checking.
* Can't take its address. Can cause unexpected behavior:
* #define SQUARE(x) x*x → SQUARE(2+3) = 2+3*2+3 = 11, not 25 */
/* const creates a typed, scoped variable */
const uint32_t max_size = 256;
/* Has a type (uint32_t), obeys scope rules, can be debugged by name.
* In embedded C, const variables are placed in flash (read-only).
* You CAN take its address: &max_size
*
* Note: On ARM, 'const' data goes in .rodata section (flash).
* This is important when flash is precious (8KB MCUs).
*/
/* In embedded specifically, BOTH are commonly used:
* - #define for register addresses and bit masks (must be compile-time constants)
* - const for lookup tables, calibration data, string constants
*/
#define GPIOA_BASE 0x40010800 /* Must be #define — used as address */
const float cal_table[] = {1.02f, 0.98f, 1.01f}; /* const — typed array */Q3: What’s wrong with this ISR code?
Bug-finding questions are popular in embedded interviews because they test real debugging skills. The interviewer wants to see if you can spot issues related to volatile, shared data access, ISR length, and reentrancy — all critical concerns in interrupt-driven embedded systems.
/* Interview question: Find the bugs */
/* Bug #1: printf in ISR — printf uses heap, can take ms to execute */
/* Bug #2: floating point in ISR — FP may not be saved/restored correctly */
/* Bug #3: Non-volatile flag — optimizer may cache it */
/* Bug #4: malloc in ISR — heap is not reentrant */
/* BUGGY VERSION: */
/*
uint8_t data_ready = 0; // Bug #3: not volatile
void ADC_IRQHandler(void) {
float voltage = ADC_DR * 3.3 / 4096.0; // Bug #2: floating point
char *buf = malloc(64); // Bug #4: malloc
sprintf(buf, "V=%.2f\n", voltage); // Bug #1: printf/sprintf
uart_send(buf);
free(buf);
data_ready = 1;
}
*/
/* CORRECT VERSION: */
volatile uint8_t data_ready = 0;
volatile uint16_t adc_raw = 0;
void ADC_IRQHandler(void) {
adc_raw = ADC_DR; /* Just grab the raw value — fast! */
data_ready = 1; /* Set flag for main loop */
/* Total ISR time: ~5 cycles. No printf, no float, no malloc. */
}
void main_loop(void) {
if (data_ready) {
data_ready = 0;
/* Process OUTSIDE the ISR where it's safe */
float voltage = adc_raw * 3.3f / 4096.0f;
printf("V=%.2f\n", voltage);
}
}Bitwise Operations Questions
Bitwise operations are the bread and butter of embedded C programming. Every register access, every protocol byte, and every hardware configuration uses bit manipulation. Interviewers use these questions to test whether you can think at the bit level — a fundamental skill for embedded work. For a comprehensive reference, see Bitwise Operations and Bit Fields in C.
/* Q4: Set bit 5 without affecting other bits */
reg |= (1 << 5);
/* Q5: Clear bit 3 without affecting other bits */
reg &= ~(1 << 3);
/* Q6: Toggle bit 7 */
reg ^= (1 << 7);
/* Q7: Check if bit 4 is set */
if (reg & (1 << 4)) { /* bit is set */ }
/* Q8: Set bits 4:2 to value 5 (101) without affecting other bits */
reg &= ~(0x7 << 2); /* Clear bits 4:2 */
reg |= (5 <> (low)) & ((1 << ((high) - (low) + 1)) - 1))
/* Q10: Is this number a power of 2? */
#define IS_POWER_OF_2(n) ((n) && !((n) & ((n) - 1)))Data Structure Questions
Embedded interview data structure questions are different from typical software engineering interviews. You will not be asked about hash maps or balanced trees. Instead, interviewers focus on structures that appear constantly in firmware: ring buffers (for UART and sensor data), state machines (for protocol handlers and device controllers), and linked lists (for memory management). Here are the two most common coding challenges.
Q11: Implement a circular (ring) buffer
The ring buffer is arguably the most important data structure in embedded systems. It appears in every UART driver (buffering received bytes), every sensor system (storing samples), and every logging framework (holding log entries). You must be able to implement one from memory. The key design decisions are: power-of-two sizing for efficient wrapping, separate read and write indices, and whether to use the “waste one slot” method or a count variable to distinguish full from empty.
/* Ring buffer — THE most common embedded interview coding question */
#define RING_SIZE 64 /* Must be power of 2 */
typedef struct {
uint8_t buf[RING_SIZE];
volatile uint16_t head; /* Write index */
volatile uint16_t tail; /* Read index */
} ring_t;
void ring_init(ring_t *r) {
r->head = 0;
r->tail = 0;
}
uint8_t ring_is_empty(ring_t *r) {
return r->head == r->tail;
}
uint8_t ring_is_full(ring_t *r) {
return ((r->head + 1) & (RING_SIZE - 1)) == r->tail;
}
int ring_put(ring_t *r, uint8_t byte) {
if (ring_is_full(r)) return -1;
r->buf[r->head] = byte;
r->head = (r->head + 1) & (RING_SIZE - 1);
return 0;
}
int ring_get(ring_t *r, uint8_t *byte) {
if (ring_is_empty(r)) return -1;
*byte = r->buf[r->tail];
r->tail = (r->tail + 1) & (RING_SIZE - 1);
return 0;
}
/* Key points interviewers look for:
* 1. Power-of-2 size for fast modulo (& instead of %)
* 2. One slot wasted to distinguish full from empty
* 3. Volatile for ISR-to-main communication
* 4. Single-producer single-consumer is lock-free
*/Q12: Implement a simple state machine
State machines are the standard design pattern for any embedded system with sequential behavior — protocol handlers, user interface flows, motor controllers, and device initialization sequences. The table-driven approach shown below is preferred over switch-case state machines because it separates the state logic from the transition data, making it easier to modify and debug. For more on this pattern, see the design patterns articles.
/* State machine for a traffic light controller */
typedef enum {
STATE_RED,
STATE_RED_YELLOW,
STATE_GREEN,
STATE_YELLOW
} traffic_state_t;
typedef enum {
EVT_TIMER,
EVT_PEDESTRIAN_BUTTON,
EVT_EMERGENCY
} traffic_event_t;
typedef struct {
traffic_state_t state;
uint32_t timer_ms;
uint32_t state_start;
} traffic_sm_t;
/* State durations */
static const uint32_t state_duration[] = {
[STATE_RED] = 30000,
[STATE_RED_YELLOW] = 2000,
[STATE_GREEN] = 25000,
[STATE_YELLOW] = 3000,
};
void traffic_sm_init(traffic_sm_t *sm) {
sm->state = STATE_RED;
sm->state_start = get_tick_ms();
}
void traffic_sm_update(traffic_sm_t *sm, traffic_event_t event) {
uint32_t elapsed = get_tick_ms() - sm->state_start;
switch (sm->state) {
case STATE_RED:
if (event == EVT_TIMER && elapsed >= state_duration[STATE_RED]) {
sm->state = STATE_RED_YELLOW;
sm->state_start = get_tick_ms();
}
break;
case STATE_RED_YELLOW:
if (elapsed >= state_duration[STATE_RED_YELLOW]) {
sm->state = STATE_GREEN;
sm->state_start = get_tick_ms();
}
break;
case STATE_GREEN:
if (event == EVT_PEDESTRIAN_BUTTON || elapsed >= state_duration[STATE_GREEN]) {
sm->state = STATE_YELLOW;
sm->state_start = get_tick_ms();
}
break;
case STATE_YELLOW:
if (elapsed >= state_duration[STATE_YELLOW]) {
sm->state = STATE_RED;
sm->state_start = get_tick_ms();
}
break;
}
/* Emergency override */
if (event == EVT_EMERGENCY) {
sm->state = STATE_RED;
sm->state_start = get_tick_ms();
}
}Protocol Questions
Protocol questions test your understanding of how embedded devices communicate. Interviewers expect you to know the physical layer (signal characteristics), the data layer (framing, addressing), and the practical trade-offs for choosing between UART, SPI, and I2C. A comparison table is the clearest way to answer.
/* Q13: Compare I2C, SPI, and UART */ /* * Feature | UART | SPI | I2C * ------------|---------------|----------------|---------------- * Wires | 2 (TX, RX) | 4+ (MOSI,MISO, | 2 (SDA, SCL) * | | SCK, CS) | * Clock | Async (no clk)| Sync (master) | Sync (master) * Speed | Up to ~1Mbps | Up to 50MHz+ | 100k-3.4MHz * Topology | Point-to-point| 1 master, N CS | Multi-master * Addressing | None | CS per device | 7-bit address * Duplex | Full | Full | Half * Distance | Up to ~15m | ~10cm (board) | ~30cm (board) * Best for | Debug, GPS, | Flash, display,| Sensors, EEPROM * | BT modules | fast ADC/DAC | RTC, I/O expand */ /* Q14: What happens if I2C pull-up resistors are missing? */ /* The bus floats — SDA and SCL never go HIGH because I2C uses * open-drain outputs. Without pull-ups: * - Logic analyzer shows signals stuck near 0V * - Communication completely fails (all NACKs or bus stuck) * - Sometimes works intermittently due to internal weak pull-ups * (some MCUs have internal ~40kΩ pull-ups — not reliable for I2C) * * Fix: Add 4.7kΩ pull-ups (standard mode) or 2.2kΩ (fast mode) * to VCC on both SDA and SCL. */ /* Q15: How do you debug an SPI device returning all 0xFF? */ /* Systematic approach: * 1. Check power: Is the SPI device getting VCC? (multimeter) * 2. Check CS: Is CS going LOW? (logic analyzer or scope) * 3. Check clock: Is SCK toggling? (scope — verify frequency) * 4. Check SPI mode: CPOL/CPHA matching between master and slave? * 5. Check MOSI: Are we sending the correct command byte? * 6. Check MISO: Is the device driving MISO at all? * (if MISO has a pull-up, 0xFF = device not responding) * 7. Check data order: MSB first vs LSB first? * 8. Check clock speed: Some devices have max SPI clock limits */
RTOS Questions
RTOS questions are standard for mid-level and senior embedded interviews. Even if the position uses bare-metal firmware, interviewers use RTOS questions to assess your understanding of concurrency, synchronization, and real-time constraints. Priority inversion is the classic RTOS interview topic because it tests both theoretical understanding and practical problem-solving.
/* Q16: What is priority inversion and how do you prevent it? */ /* Priority inversion occurs when: * - High-priority task H needs a resource locked by low-priority task L * - Medium-priority task M preempts L * - H is blocked waiting for L, but L can't run because M is running * - Result: H (highest priority) waits for M (medium) — inverted! * * Prevention: * 1. Priority inheritance: When H blocks on L's mutex, L temporarily * inherits H's priority, preempting M, finishing quickly, releasing mutex. * 2. Priority ceiling: Mutex has a "ceiling" priority = highest priority * task that uses it. Any task holding the mutex runs at ceiling priority. * * FreeRTOS uses priority inheritance by default with xSemaphoreCreateMutex() */ /* Q17: What's the difference between a mutex and a binary semaphore? */ /* Binary semaphore: Signaling mechanism. Any task can give/take. * Used for: ISR-to-task notification, event signaling. * * Mutex: Ownership mechanism. Only the task that took it can give it. * Used for: Protecting shared resources (exclusive access). * Bonus: Priority inheritance to prevent priority inversion. * * Common mistake: Using a binary semaphore to protect shared data. * This works but doesn't provide priority inheritance. */ /* Q18: How much stack should you allocate per task? */ /* Start with a generous amount (1-2KB), then: * 1. Use uxTaskGetStackHighWaterMark() to find actual usage * 2. Reduce to actual_usage × 1.5 (safety margin) * 3. Watch for: printf (uses ~512+ bytes), deep call chains, * large local arrays, recursive functions * Stack overflow is the #1 cause of RTOS crashes. */
Debugging Scenario Questions
Scenario-based debugging questions are where experienced candidates shine. The interviewer describes a vague symptom — “the system crashes randomly” or “UART communication works for a while then stops” — and evaluates your systematic approach to diagnosis. They are not looking for the answer; they are looking at your process. Strong candidates ask clarifying questions, identify likely root causes, and describe specific diagnostic steps using real tools.
/* Q19: "The system crashes randomly every few hours. How do you debug it?" * * Systematic approach: * 1. Add a HardFault handler that logs the faulting PC address * and fault status registers to a persistent log * 2. Enable watchdog timer — it will reset the system on crash * 3. Add post-mortem logging (survives reset) to capture events * before the crash * 4. Check for stack overflow (configure stack canaries or MPU) * 5. Look for memory corruption: * - Array out-of-bounds writes * - Use-after-free (if using dynamic allocation) * - Unprotected shared variables (missing volatile or mutex) * 6. Check interrupt priorities — are ISRs preempting each other * incorrectly? * 7. Monitor power supply — random crashes often = brownout * 8. Check for race conditions between ISRs and main code */ /* Q20: "The UART receives garbage characters. What do you check?" * * 1. Baud rate match — most common cause. Verify with oscilloscope: * measure bit width, calculate actual baud rate. * 2. Clock accuracy — is the MCU running at the expected frequency? * Internal RC oscillator can be ±1-2% off. * 3. Voltage levels — 3.3V MCU connected to 5V device without * level shifting? Or RS-232 levels without a MAX3232? * 4. TX/RX swap — TX should connect to RX and vice versa. * 5. Ground connection — does the UART cable share GND? * 6. Data format — 8N1 vs 8E1? Parity mismatch causes errors. * 7. Flow control — is RTS/CTS expected but not connected? */
Memory and Architecture Questions
These questions test your understanding of how code maps to hardware — where variables live in memory, how the startup code initializes .data and .bss, and what happens between power-on and main(). This knowledge is essential for debugging hard faults, optimizing memory usage, and writing linker scripts. See Memory Mapping in Embedded Systems for background.
/* Q21: Describe the memory sections of a bare-metal embedded program */
/*
* Flash (ROM):
* .text — Executable code (functions)
* .rodata — Read-only data (const, strings)
* .data_init — Initial values for .data (copied to RAM at startup)
*
* RAM:
* .data — Initialized global/static variables (copied from flash)
* .bss — Zero-initialized global/static variables
* .heap — Dynamic allocation (malloc) — grows UP
* .stack — Local variables, return addresses — grows DOWN
*
* Startup code (Reset_Handler) does:
* 1. Copy .data_init from flash to .data in RAM
* 2. Zero-fill .bss in RAM
* 3. Initialize stack pointer
* 4. Call main()
*/
/* Q22: What is the 'volatile const' use case? */
/* A variable that the compiler should not optimize reads for (volatile)
* but the code should not modify (const).
* Example: A read-only hardware status register.
*
* volatile const uint32_t *STATUS = (volatile const uint32_t *)0x40004014;
* The hardware changes it (volatile), but our code only reads it (const).
*/
/* Q23: What does 'static' mean in different contexts? */
/* 1. Static local variable: retains value between function calls
* void count(void) { static int n = 0; n++; }
*
* 2. Static global variable: limits scope to this .c file (internal linkage)
* static int module_state = 0; // Not visible to other files
*
* 3. Static function: limits scope to this .c file
* static void helper(void) { } // Private to this module
*/Interview Tips
Beyond technical knowledge, how you present your answers matters. Embedded interviews are as much about demonstrating your engineering mindset — systematic thinking, awareness of edge cases, and practical experience — as they are about getting the right answer.
- Think out loud: Interviewers want to see your thought process, not just the answer. Explain why you’re choosing an approach.
- Ask clarifying questions: “What MCU architecture? What constraints? Real-time requirements?” shows you think like an engineer.
- Start with the simple solution: Then discuss trade-offs and improvements. Don’t over-engineer the first pass.
- Know your projects: Be ready to explain design decisions, trade-offs, and what you’d do differently in your portfolio projects.
- Practice on paper/whiteboard: Embedded interviews often don’t use online coding environments. Practice writing C on a whiteboard.
Related Articles
- How to Become an Embedded Systems Engineer
- Embedded Systems Career Roadmap
- Bitwise Operations and Bit Fields in C
- Inline Functions and Macros in Embedded C
- What Is an RTOS?
- Volatile Keyword in C
- Bitwise Operators in C
- Complete Guide to RTOS
- Priority Inversion Problem in RTOS
- Communication Interfaces: UART, SPI, and I2C
- Pointers in C
📖 Related: Mutual Exclusion in RTOS: Mutexes, Critical Sections, and Atomics • Memory Management Strategies for RTOS Applications

Vivek Bhageria — Lead Firmware R&D Engineer, 12+ years. Ex-Bosch (automotive powertrain), MusicTribe (real-time audio), medical devices. M.Tech BITS Pilani. I write at NerdyElectronics — practical, register-level embedded systems for engineers who want to understand what’s actually happening under the hood.





