The same disciplined approaches as good C, plus C++ niceties. The baseline is status-code returns, a function returns an enum class Error { Ok, Nack, Timeout, ... } and the caller checks it and handles locally (retry, log, fail safe). For functions that must return both a value and a possible error, use a result type: std::expected<T, E> (C++23) or a hand-rolled/library Result<T, E> that holds either a value or an error, so the caller is forced by the type to handle both cases before extracting the value. You can also use out-parameters with a status return (the C idiom). The key properties you preserve are that errors are explicit in the signature, checkable, and propagate without unwinding the stack non-deterministically. RAII still works alongside this, destructors clean up on the error return path just as they would during exception unwinding, so you keep leak-free cleanup without the exception machinery.
C++ for Embedded · Interview question
How do you report errors in C++ firmware without exceptions?
A strong answer
What a weak answer sounds like
You know the answer. Do you know what gets you dinged?
Pro breaks down the answer most candidates actually give to this question — and the specific reason an interviewer marks it down. It’s the difference between sounding correct and sounding senior, on all 472 questions.
From the lesson
What to Avoid on MCUs
The three C++ features that cost more than they're worth on a microcontroller (exceptions, RTTI, and dynamic allocation): why they hurt, and the deterministic patterns to use instead.