What You Actually Need to Know for Embedded C Interviews
Most people walk into embedded C interviews thinking they need to memorize syntax. They don't. I've sat on both sides of that table more times than I can count, and the ones who get hired are the ones who understand what the hardware is doing at a fundamental level. The questions themselves tend to cluster around a few recurring themes, and once you've seen the pattern, you can prepare for them without drowning in random practice problems. The first category you'll encounter is bit manipulation. This isn't just about knowing the operators exist. Interviewers want to see that you understand when and why you'd use them in real code. Setting a bit in a GPIO register, clearing a flag, toggling an interrupt status — these are everyday operations. A common question asks you to write a macro that sets the 5th bit of a variable without affecting any other bits. The expected answer involves a bitwise OR with a left-shifted one. But here's where things get interesting. Someone might follow up by asking you to do it as a single expression without using a temporary variable, or they might ask about endianness implications when you're manipulating bytes across different architectures. I remember a candidate once wrote the macro correctly but then got burned when I asked what happens if the input value is larger than the target type can hold. The shift operation invoked undefined behavior because they hadn't considered overflow before shifting. That's the difference between someone who's written embedded code and someone who's read about it.
The second category is pointers and memory. This is where most people struggle, even experienced developers. Questions about pointer arithmetic, function pointers, const correctness, and the difference between int *const and const int * come up constantly. The tricky part isn't answering these correctly in isolation. It's answering them when the interviewer adds constraints like "don't use arrays" or "write this without dereferencing the pointer explicitly." I had a situation where I needed to swap two values in place without a temporary variable, using only pointer arithmetic and XOR. The straightforward XOR swap works for most cases, but it fails when both pointers reference the same memory location. I wrote that down during an interview once, explained the edge case, and offered the conditional workaround instead. The interviewer nodded and moved on. That specific interaction — acknowledging the failure mode rather than pretending the XOR swap was universally correct — is worth more than any memorized answer. Data structures in embedded C are another heavy area. You'll be asked to implement a linked list, a queue, or a ring buffer. The ring buffer question is especially common because it shows whether you understand both the algorithm and the hardware constraints. A basic implementation uses head and tail indices with modulo arithmetic. But a good answer addresses what happens when the buffer is full or empty, how to handle interrupts that might modify the buffer concurrently, and whether you should use a power-of-two size to replace the modulo with a bitwise AND.
Speaking of interrupts, the volatile keyword is practically guaranteed to come up. Not just what it does, but why you need it in specific contexts. A variable shared between an ISR and the main loop must be declared volatile, otherwise the compiler may cache it in a register and never see updates from the interrupt. The counter-intuitive part that most beginners miss is that volatile does not make code atomic. On an ARM Cortex-M, a 32-bit load from volatile memory can be interrupted mid-operation if a higher-priority interrupt fires, leaving the main code with a partially updated value. I've seen production code crash because someone added volatile to fix a compiler warning without actually protecting the shared data with proper synchronization. Real-time operating systems come up frequently too. You'll need to explain task scheduling, priority inversion, and how to resolve it using priority inheritance or priority ceiling protocols. Semaphore versus mutex is another classic distinction. A semaphore is a signaling mechanism. A mutex is a locking mechanism with ownership semantics. In FreeRTOS, for example, a binary semaphore and a mutex look similar at first glance, but they behave very differently when a task holding the mutex is preempted by a higher-priority task that needs the same mutex. Priority inheritance kicks in for the mutex and temporarily boosts the lower-priority task's priority. The semaphore does not do this. Memory management in embedded systems is another topic that separates the practiced from the casual. malloc and free are generally discouraged in hard real-time systems because of fragmentation and unpredictable allocation time. You'll often be asked to write your own allocator or explain why static allocation is preferred. I once worked on a medical device where we used a slab allocator with fixed-size blocks. It eliminated fragmentation entirely and made allocation time deterministic at roughly 12 microseconds per block on our ARM7. That determinism mattered because the device had hard timing deadlines for dose calculations.
Get the Full Details

Compiler and optimization behavior is the category most people don't study for until it's too late. You need to understand what the compiler can and cannot reorder, how inline functions work versus macros, and what __attribute__((packed)) actually does to your structure layout. A common trap question shows a structure with mixed-size fields and asks for the sizeof result. The answer depends on alignment rules specific to the target architecture. On a 32-bit ARM, the compiler pads structures to word boundaries unless packing is requested. On a 68K, alignment behavior is different. Interviewers who know what they're doing will ask about this specifically because it reveals whether you've actually dealt with cross-compilation issues. Low-level hardware interaction questions round out the technical portion. Register mapping, memory-mapped I/O, peripheral initialization sequences, and clock configuration are all fair game. One question I frequently asked is simple on the surface: "Write a function that reads a 16-bit value from a memory-mapped register, checks a status bit, and returns either the value or an error code." The straightforward implementation gets you halfway there. The complete answer addresses whether the read needs to be an exact-width access, whether barriers are required on architectures like ARM where relaxed memory models allow reordering, and whether the status bit could change between the read and the check without you noticing. There's also the debugging angle. You'll be asked how you'd approach a bug where a variable occasionally has the wrong value, or where the system hangs sporadically. A good response mentions logic analyzers, JTAG debugging, printf-based tracing with ring buffers, and the importance of reproducing the issue under controlled conditions. I've spent entire days tracking down bugs that turned out to be stack overflows, compiler bugs in older GCC versions, or even silicon errata on the MCU itself. Each one taught me to write more defensive code and to treat sporadic failures as the most expensive kind of bug.
The practical test component is where theory meets execution. Many companies give you a small coding exercise on a whiteboard or a shared editor. You might be asked to parse a CAN message, implement a CRC calculation, or write a state machine for a simple protocol. The expectations aren't about perfect code. They're about your thought process. Do you ask clarifying questions? Do you consider edge cases before writing? Do you write tests or at least trace through examples manually? One tip that actually helps: write your code in a way that makes your assumptions explicit. If you assume a certain register layout, say so. If you're making an assumption about compiler behavior, note it. Interviewers care more about whether you can reason through uncertainty than whether you've seen the exact problem before. Embedded C work is full of uncertainty because you're dealing with hardware that has quirks, compilers that have optimizations you didn't account for, and requirements that shift as the project progresses. Don't waste time on questions about languages that aren't C unless they specifically ask. Deep knowledge of C is much more valuable than shallow knowledge of C++ or Python for these roles. If they want C++, they'll ask about templates, RAII, and std containers. If they want C, they want to know whether you understand the ABI, the calling convention, and what the compiler is actually generating for your code.
Finally, don't overprepare by memorizing answers. The interviewers can tell. Prepare by understanding concepts well enough that you can adapt them to unfamiliar variations. Read the datasheet of a microcontroller you've never used before and try to write a driver for one of its peripherals from scratch. That exercise alone will cover more of what actually comes up than any list of practice questions.