Embedded software engineers write C and C++ firmware for automotive ECUs, medical devices, telecom hardware and consumer electronics. They work with limited memory, real-time constraints and hardware interfaces such as UART, SPI and CAN. Interviews test pointers, memory layout, bit manipulation, volatile, interrupts and multithreading basics. Electronics and computer science graduates join automotive and semiconductor companies in Bengaluru, Pune and Hyderabad in this role.
Pointers and memory Bit manipulation RTOS basics Hardware interfaces Debugging on target
Solved for this roleStart practicing
3 QUESTIONS FREESign up to unlock the full question bank and track your progress.Unlock every question and answer, then continue practising company-wise and package-wise.
✓ 3 questions free • Unlock the rest in seconds
01
A function returns a reference to a local variable and the code sometimes works. Explain what is happening.
The local is destroyed when the function returns, so the reference dangles and using it is undefined behaviour. It may seem to work because the old bytes remain on the stack until another call overwrites them. Compilers warn with -Wreturn-local-addr or -Wreturn-stack-address. The fix is to return by value; copy elision and move semantics make returning objects cheap.
A data race occurs when two threads access the same memory at the same time, at least one writes, and there is no synchronisation; in C++ this is undefined behaviour. A race condition is a broader logic bug where the result depends on timing, such as check-then-act on a file or a balance, even if each access is atomic. Mutexes, atomics and careful design of shared state prevent both.
03
What is the difference between delete and delete[]?
delete destroys a single object created with new. delete[] destroys an array created with new[] and calls the destructor of every element, using an element count the runtime stores with the allocation. Using delete on an array or delete[] on a single object is undefined behaviour and can leak or corrupt the heap. In modern code std::vector or std::unique_ptr<T[]> removes the need for either.
vector stores elements contiguously, gives O(1) random access and fast iteration, with amortised O(1) push_back but O(n) insertion in the middle. list is a doubly linked list with O(1) insert and erase anywhere given an iterator, but no random access and poor cache use. deque supports fast insertion at both ends and random access, stored in chunks. Default to vector unless profiling says otherwise.
05
What is the difference between const int*, int* const and const int* const?
Read the declaration from right to left. const int* p is a pointer to a const int: you cannot modify the value through p, but p can point elsewhere. int* const p is a const pointer to int: the address is fixed but the value can change. const int* const p fixes both the address and the value. These forms are frequent interview traps and appear in function signatures.
06
Tell me about a time you found a difficult bug in your C++ code.
Situation: a service crashed randomly about once a day in production. Task: find the root cause without blocking releases. Action: I enabled core dumps, analysed them in gdb and saw a corrupted vector inside a callback. A staging run with AddressSanitizer showed a use-after-free because a lambda captured a raw pointer to a destroyed object. I changed it to capture a weak_ptr. Result: no crashes for months, and sanitizer runs were added to CI.
07
Write a swap function using references and explain why a pass-by-value version fails.
void swapByValue(int a, int b) { // works on copies, caller sees no change
int t = a; a = b; b = t;
}
void swapByRef(int& a, int& b) { // a and b alias the caller's variables
int t = a;
a = b;
b = t;
}
int x = 1, y = 2;
swapByRef(x, y); // now x == 2, y == 1
08
Two third-party libraries both define a global function called init and your build fails with duplicate symbols. How do you fix it?
The clean fix is to place each library's code in its own namespace so calls become liba::init and libb::init. If the source cannot change, I would check whether one function is only used internally and give it internal linkage, or wrap one library behind a shared library that hides its symbols using visibility settings. I avoid macro renaming hacks because they break silently on upgrades.
09
What is the difference between stack and heap memory?
Stack memory holds local variables and call frames; allocation is very fast and automatic, and memory is freed when the scope ends, but the size is limited to a few megabytes. Heap memory is allocated at run time with new or by containers, is much larger and lives until it is freed, but allocation is slower and can fragment. Large, variable-size or long-lived objects belong on the heap.
10
What is the difference between a shallow copy and a deep copy?
A shallow copy copies pointer members, so two objects share one heap buffer; destroying one leaves the other dangling and causes a double free. A deep copy allocates new memory and copies the contents. The compiler-generated copy constructor copies member by member, so a class owning a raw pointer needs a custom copy constructor and copy assignment, or better, should hold a std::vector or unique_ptr instead.
11
Write a C++ program that counts how often each word appears in the input.
#include <iostream>
#include <map>
#include <string>
int main() {
std::map<std::string, int> freq;
std::string word;
while (std::cin >> word) {
++freq[word];
}
for (const auto& [w, c] : freq) {
std::cout << w << ' ' << c << '\n';
}
}
// Use std::unordered_map for O(1) average lookups if sorted output is not needed.
A deadlock happens when threads wait forever for each other's locks, for example thread A holds m1 and waits for m2 while thread B holds m2 and waits for m1. It needs mutual exclusion, hold and wait, no preemption and circular wait. Prevent it by always locking in a fixed global order, locking several mutexes together with std::scoped_lock, keeping critical sections small and never calling unknown code while holding a lock.
13
What are the fundamental data types in C++ and their typical sizes?
The main types are bool, char, short, int, long, long long, float, double and long double. On a typical 64-bit Linux system char is 1 byte, short 2, int 4, long 8, long long 8, float 4 and double 8. The standard only guarantees minimum ranges, so check with sizeof, and use fixed-width types such as int32_t or uint64_t from <cstdint> when the exact size matters.
14
What is the difference between struct and class in C++?
The only language difference is default access. Members and base classes of a struct are public by default, while in a class they are private by default. Both can have constructors, member functions, inheritance and templates. By convention a struct is used for simple data aggregates such as a Point, and a class is used for types that protect invariants with private state.
15
Compare std::mutex, lock_guard, unique_lock and scoped_lock.
std::mutex provides lock and unlock, but calling them manually is error-prone. lock_guard is a simple RAII wrapper that locks in the constructor and unlocks in the destructor. unique_lock is more flexible: it can defer locking, unlock early, be moved and is required by condition_variable. scoped_lock, from C++17, locks one or more mutexes at once using a deadlock-avoidance algorithm.
A dangling pointer points to memory that has already been freed or has gone out of scope, for example the address of a local variable returned from a function, or a pointer used after delete. Dereferencing it is undefined behaviour and may crash much later in unrelated code. Prevent it with smart pointers, clear ownership rules and by never returning addresses of local variables.
18
Compare pass by value, pass by pointer and pass by reference.
Pass by value copies the argument, so changes inside the function do not affect the caller. Pass by pointer passes an address, can be null and needs dereferencing. Pass by reference creates an alias that cannot be null and uses normal syntax. A good rule: pass small types such as int by value, large read-only objects by const reference, and use a non-const reference or pointer for output parameters.
19
What is CMake and why is it used for C++ projects?
CMake is a cross-platform build system generator. You describe targets, source files, include paths and dependencies in CMakeLists.txt, and CMake generates Makefiles, Ninja files or Visual Studio projects. Modern CMake is target based, using target_link_libraries and target_include_directories so settings flow to dependents. It integrates with package managers such as vcpkg and Conan and with CTest for running tests.
20
What is function overloading and how does the compiler choose an overload?
Overloading means several functions share a name but differ in the number or types of parameters; return type alone is not enough. The compiler runs overload resolution: exact matches win, then promotions such as char to int, then standard conversions, then user-defined conversions. If two candidates are equally good the call is ambiguous and compilation fails. Name mangling gives every overload a unique linker symbol.
21
A teammate added using namespace std; to a shared header file. What problems can this cause?
Every file that includes the header now has all std names pulled into the global namespace. This can cause ambiguous calls or silently pick the wrong function when user code defines names like count, distance, min or data. The problem spreads to every dependent file and is hard to trace. I would remove it and use explicit std:: qualification or narrow using declarations inside .cpp files or function bodies.
22
Four threads each increment a shared int one million times, but the final total is less than four million. Why?
counter++ is a read-modify-write of three steps, so threads interleave and overwrite each other's updates, which is a data race and undefined behaviour. Fix it with std::atomic<int> and fetch_add, which is fast for a simple counter, or protect the variable with a mutex. Even better for performance, let each thread count in a local variable and add it to the total once at the end.
23
How do you create a thread in C++ and what is the difference between join and detach?
std::thread t(func, args); starts a new thread running func. join() blocks until the thread finishes, so the caller can safely use its results. detach() lets the thread run independently; you lose the handle and it must not touch objects that may be destroyed. If a joinable std::thread is destroyed without join or detach, std::terminate is called. C++20 std::jthread joins automatically in its destructor.
24
What does the static keyword mean in different contexts?
Inside a function, a static local variable is initialised once and keeps its value between calls. At namespace scope, static gives internal linkage, so the name is visible only in that translation unit. Inside a class, a static data member belongs to the class rather than each object, and a static member function has no this pointer. Since C++17 inline static members can be defined directly in the header.
25
A program that prints a million lines with std::endl is very slow. Why, and how do you fix it?
std::endl writes a newline and also flushes the stream buffer every time, forcing a system call per line. Replace it with the newline character so output is buffered and written in large chunks. For competitive programming or bulk output, also call std::ios::sync_with_stdio(false) and std::cin.tie(nullptr). Flush explicitly only when output must appear immediately, such as before a risky operation.
26
Why did you choose C++ for your career instead of a language like Python or Java?
Situation: in college I built a small game and a file compressor in Python and hit performance limits. Task: I wanted to understand what happens closer to the hardware. Action: I learned C++ properly, including memory management, STL and multithreading, and rewrote the compressor. Result: it ran about ten times faster and I understood systems much better. I enjoy work where that control matters, while I still use Python for scripting.
27
What is the difference between a process and a thread?
A process is an independent program with its own address space, file handles and resources; processes are isolated and communicate through IPC such as pipes, sockets or shared memory. Threads live inside a process and share its memory and handles, but each has its own stack and registers. Threads are cheaper to create and communicate faster, but shared memory needs synchronisation to avoid races.
A leak happens when heap memory is allocated but never freed and the last pointer to it is lost, so memory use keeps growing. In long-running servers this eventually exhausts RAM. Detect leaks with Valgrind memcheck, LeakSanitizer through -fsanitize=address, or heap profilers. Prevent them with RAII types such as std::unique_ptr and std::vector, which release memory automatically.
#include <chrono>
#include <iostream>
template <typename F>
long long timeMs(F&& f) {
auto start = std::chrono::steady_clock::now();
f();
auto end = std::chrono::steady_clock::now();
return std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count();
}
// usage
// auto ms = timeMs([] { heavyWork(); });
// Use steady_clock, not system_clock, because it never jumps backwards.
A void* can hold the address of any object type but cannot be dereferenced or used in arithmetic until it is cast back to a real type. It appears in C APIs such as malloc, memcpy and callback user-data parameters. In C++ code, templates, std::any or std::variant are better because void* removes type safety and mistakes appear only at run time.
32
What are default arguments and what rules apply to them?
Default arguments supply values when a caller leaves out trailing arguments, for example void log(const std::string& msg, int level = 1);. Defaults must appear at the end of the parameter list and be given only once, usually in the header declaration. They are bound at compile time, so for a virtual function the default value comes from the static type of the pointer, not the dynamic type, which is a common trap.
33
A program crashes with terminate called without an active exception when a function returns. What is the cause?
A std::thread object that is still joinable was destroyed, often because the function returned early or threw before reaching join. The std::thread destructor calls std::terminate in that case. Fix it by making sure every thread is joined on all paths, using an RAII guard, or switching to std::jthread, which joins automatically. Be careful with detach, since detached threads may access destroyed data.
Adding an integer to a pointer moves it by that many elements, not bytes, so p + 1 on an int* advances by sizeof(int) bytes. Subtracting two pointers into the same array gives the number of elements between them as ptrdiff_t. Arithmetic is only valid inside an array or one past its end; moving beyond that is undefined behaviour. Contiguous iterators work on the same idea.
35
What does C++20 std::jthread add over std::thread?
std::jthread joins automatically in its destructor, so forgetting join no longer calls std::terminate. It also supports cooperative cancellation: the thread function can take a std::stop_token, check stop_requested() in its loop, and the owner calls request_stop(), which the destructor also does before joining. std::stop_callback lets you register cleanup actions. This makes long-running worker threads safer to write.
36
Write an efficient function to check whether a number is prime.
bool isPrime(long long n) {
if (n < 2) return false;
if (n % 2 == 0) return n == 2;
for (long long i = 3; i * i <= n; i += 2) {
if (n % i == 0) return false;
}
return true;
}
// Only odd divisors up to sqrt(n) are checked: O(sqrt n) time.
++i increments the variable and returns the updated object, while i++ returns a copy of the old value and then increments. For an int the optimiser makes them equally fast. For iterators and class types i++ must create a temporary copy, so ++i is preferred in loops. Example: int a = 5; int b = a++; leaves b equal to 5 and a equal to 6.
38
Describe how you handled an urgent production issue while on call.
Situation: at 2 AM our order service started timing out. Task: restore service quickly and find the cause. Action: I followed the runbook, checked dashboards, saw thread pool saturation after a new release, rolled back within 15 minutes and posted updates in the incident channel. Next day I found a blocking database call inside a worker thread. Result: service was restored fast, and I added a timeout and an alert on queue depth.
39
Find the middle node of a linked list in a single pass.
Node* middle(Node* head) {
Node* slow = head;
Node* fast = head;
while (fast && fast->next) {
slow = slow->next;
fast = fast->next->next;
}
return slow;
}
// fast moves two steps and slow one step, so slow stops at the middle.
40
What is the difference between a declaration and a definition?
A declaration tells the compiler that a name of a certain type exists, for example extern int count; or void f(int);. A definition actually allocates storage or provides the function body. You may declare a name many times but define it only once across the whole program, which is the One Definition Rule. Headers normally hold declarations and .cpp files hold definitions.
Frequently asked questions
What does a Embedded Software Engineer do?
Embedded software engineers write C and C++ firmware for automotive ECUs, medical devices, telecom hardware and consumer electronics. They work with limited memory, real-time constraints and hardware interfaces such as UART, SPI and CAN. Interviews test pointers, memory layout, bit manipulation, volatile, interrupts and multithreading basics. Electronics and computer science graduates join automotive and semiconductor companies in Bengaluru, Pune and Hyderabad in this role.
Which skills are tested in a Embedded Software Engineer interview?
Expect questions on Pointers and memory, Bit manipulation, RTOS basics, Hardware interfaces, Debugging on target. Most rounds also ask you to explain a project and solve one practical problem live.
How do I prepare for a Embedded Software Engineer interview?
Revise Pointers and Memory Management, C++ Basics and Syntax, Multithreading and Concurrency first using the topic sheets, practise the questions on this page aloud, then do one full mock interview to practise timing.