Free interview practice · Embedded C

Embedded C Interview Preparation

Technical questions with practical firmware reasoning, code examples and follow-ups. Built for engineers preparing for real interviews.

10 practice questions · Start by answering aloud before opening the explanation.

Q01 Why is volatile used for memory-mapped registers?

A peripheral may change a register outside normal program flow. A volatile-qualified access tells the compiler the register must be read or written as expressed, rather than assuming the value stays unchanged.

volatile uint32_t *status = (volatile uint32_t *)0x40000000u;

Interview follow-up: Does volatile make a read-modify-write operation atomic? No.

Q02 Can two consecutive reads of a volatile register differ?

Yes. The peripheral may update status between the reads, and some registers clear flags on read. Consult the device reference manual before accessing a status register repeatedly.

uint32_t a = *status;
uint32_t b = *status;

Interview follow-up: How would you handle a read-to-clear status register?

Q03 What is the difference between const volatile and volatile?

volatile means each access is observable to the compiler. const disallows modifications through that particular C lvalue. A read-only hardware status register is often declared const volatile.

const volatile uint32_t *status = (const volatile uint32_t *)0x40000000u;

Interview follow-up: Does const prevent hardware from updating the register? No.

Q04 What happens if a pointer to a hardware register is not volatile?

The compiler may legally reuse an earlier value or hoist a read from a polling loop, depending on the code and optimization. Cast the register address to the correct volatile-qualified pointer type.

volatile uint32_t *reg = (volatile uint32_t *)0x40000000u;

Interview follow-up: Is declaring the pointer variable volatile the same as declaring the pointed-to register volatile? No.

Q05 How do you set a bit without changing other bits?

Read the register, OR the bit mask, then write it back. This is a read-modify-write operation, which can be unsafe for write-one-to-clear registers and when another context updates the same register.

*control |= (1u << 3);

Interview follow-up: What hardware atomic set/clear registers could you use instead?

Q06 How do you clear a bit using a mask?

AND the value with the inverse of the target bit mask. Confirm that the register permits normal read-modify-write access before using this pattern.

*control &= ~(1u << 3);

Interview follow-up: Why is this incorrect for some write-one-to-clear registers?

Q07 What is the difference between a pointer and an address?

An address is a numeric location; a pointer is a typed value used by C to refer to an object or function. In embedded firmware, integer-to-pointer casts for peripheral addresses depend on platform mapping and implementation.

volatile uint32_t *reg = (volatile uint32_t *)0x40000000u;

Interview follow-up: What does *reg mean in this statement?

Q08 Why does volatile not make shared data thread-safe?

Volatile does not provide atomicity or synchronization. A read-modify-write counter update can race, and coordination between tasks or cores needs the appropriate synchronization/atomic primitives.

counter++; /* not necessarily atomic */

Interview follow-up: What changes when an ISR shares a flag with foreground code?

Q09 Why can structure padding matter in firmware?

The compiler may add padding between members for alignment, so the memory layout can differ from a wire protocol. Use explicit serialization instead of assuming a packed C structure matches UART/SPI packet bytes.

struct Packet { uint8_t type; uint32_t value; };

Interview follow-up: How would you serialize a multi-byte value with defined byte order?

Q10 What happens if an interrupt occurs during a read-modify-write?

An ISR can update the same register or variable between the read and write, potentially losing one update. Consider atomic set/clear hardware, critical sections, or suitable synchronization based on context.

uint32_t t = *reg;
t |= BIT;
*reg = t;

Interview follow-up: Why might disabling interrupts not solve a second-core race?