C++ Module 13
Error handling interview questions
19 interview questions on error handling from C++: short warm-ups, then what each harder question tests, the first step of a good answer and its follow-ups.
Warm-up questions
Short questions with short answers, each answered in full.
-
Fresher
Why do you throw by value and catch by const reference?
Answered in the lesson C++ exceptions: throw, catch and stack unwinding
-
Fresher
What are the basic, strong and nothrow exception-safety guarantees?
-
Fresher
What does std::expected<T, E> hold, and how is it different from std::optional<T>?
Answered in the lesson std::expected vs exceptions vs error codes
-
Fresher
What is the difference between assert and static_assert?
Answered in the lesson Assertions, preconditions and [[assume]]
-
Fresher
What is a backtrace, and how do you get one from a C++ program that crashed?
Answered in the lesson Debugging crashes: backtraces and std::stacktrace
Questions with model answers
What each question tests, the first step of a good answer and the follow-ups to expect.
-
Fresher to mid
A batch job dies with "terminate called after throwing an instance of 'std::runtime_error'" and exit status 134. Its log, written by a class that flushes buffered lines in its destructor, is missing the last lines. Explain both symptoms and what you would change.
- What it tests
- What happens when no handler matches, why no destructor ran, what exit status 134 means, and catching in main against keeping the place of the throw for diagnosis.
- First step
- The message says no handler matched, so std::terminate ran. Both GCC and Clang follow the Itanium C++ ABI, which looks for a handler before it unwinds anything; with none found, no destructor ran, so the log's buffer was never flushed. 134 is 128 plus 6: std::terminate called std::abort, which raises SIGABRT.
- Follow-ups
-
- Could the same program flush its log when built with another implementation?
- Once main catches everything, how do you still learn where the exception was thrown?
- Should main also catch exceptions that signal a bug?
From the lesson C++ exceptions: throw, catch and stack unwinding
Model answer, Pro. Model answers open with any pass; prices are in the notice on this page.Opening the model answers with your pass…
Model answers open with any MySmartCoPilot pass.
- Pro pass · 1 week₹79$2.99
- 30 days₹179$5.99
- 1 year₹1,699$45
One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.
Passes can’t be bought right now, so the model answers can’t be opened at the moment. If you have a pass, enter your licence key.
It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).
In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Mid to senior
You are designing the exceptions of a payments client library that several teams will use. Which classes do you define, what do they derive from, what data do they carry, and how should callers in different layers catch them?
- What it tests
- Designing an exception hierarchy around the decisions callers make, standard base classes, context carried in members that copy without throwing, and never making what() part of the interface.
- First step
- Start from what a caller must do differently: retry later, fix its request, or give up and report. Define one base, PaymentError, derived from std::runtime_error, and one subclass for each of those decisions, each carrying the data its handler needs, such as a retry delay or the name of the invalid field.
- Follow-ups
-
- Should a declined card be an exception?
- How do you stop callers from depending on what() messages?
- One consuming team builds with -fno-exceptions. What do you give them?
From the lesson C++ exceptions: throw, catch and stack unwinding
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Fresher to mid
Inventory::transfer(item, from, to, qty) subtracts qty from one warehouse's std::map<std::string, int> and adds it to another's. After a std::bad_alloc in production, some stock disappeared. Make transfer strongly exception-safe, and explain how you would test it.
- What it tests
- Finding the step that can throw, doing every allocation before any visible change, validating first, and proving the fix with fault injection.
- First step
- Find the operation that can throw. Changing an int that already exists cannot fail, but to[item] may insert a new node, which allocates. If that comes after from[item] -= qty, a failed allocation loses the stock, so do the insertion first and change both counts only afterwards.
- Follow-ups
-
- What if the item has no entry in the destination warehouse yet?
- How would a test prove the fix?
- Does the guarantee still hold when two threads transfer at once?
From the lesson Exception safety guarantees in C++
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Mid to senior
A teammate wants every member function of your types to give the strong guarantee, starting with the assignment operator of a Matrix that holds a 1 GiB buffer. When is the strong guarantee too expensive, and what do you promise instead?
- What it tests
- The memory and time that copy-and-swap costs on a large object, the basic guarantee the standard library itself gives, and letting callers opt in to the strong guarantee.
- First step
- Work out the cost first. Copy-and-swap assignment of a 1 GiB matrix allocates a second gigabyte and copies all of it, even when the target already has a buffer of the right size. Then ask what callers gain: whether they would use a half-assigned matrix at all, or discard it after the exception.
- Follow-ups
-
- Which standard library operations give only the basic guarantee, and why?
- How can a caller who needs the strong guarantee still get it?
- Where would you insist on the strong guarantee anyway?
From the lesson Exception safety guarantees in C++
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Mid to senior
A move-only Connection type re-registers its socket with an event loop in its move constructor, which can therefore throw. A std::vector of connections sometimes holds dead connections after a std::bad_alloc. Explain what happened and how you would fix the type.
- What it tests
- How std::vector relocates elements when it grows, why a throwing move of a type that cannot be copied leaves unspecified results, and designing moves that cannot fail.
- First step
- Start from what the vector does when it outgrows its buffer: it relocates every element into a new one. A Connection cannot be copied, so the vector has to use the move constructor, which can throw. Then work out what a relocation that fails halfway leaves in the old buffer and in the new one.
- Follow-ups
-
- Why does std::vector copy elements whose move may throw, when it can?
- How do you make the move noexcept when registration can fail?
- How would a test have caught this before production?
From the lesson Exception safety guarantees in C++
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Mid to senior
You are writing the parser layer of a log-processing library. Should its functions return std::expected with an enum, with std::error_code, or with a struct of context as the error, and how do callers in the other layers use what you return?
- What it tests
- Choosing an error type by what callers do with it, the cost of rich errors, converting errors between layers with transform_error, and when errors must mix with system errors.
- First step
- List what callers do with a failure: skip the line, show it to a person, count failures by kind, or report a system error from below. A small struct with an enum kind and a position serves the first three; std::error_code earns its place only where errors must mix with system errors.
- Follow-ups
-
- Callers also want the file name and the line number. Which layer adds them?
- How do you keep the error type cheap to return when nothing fails?
- When would this library still throw?
From the lesson std::expected vs exceptions vs error codes
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Mid to senior
A game engine builds with -fno-exceptions, and about 400 functions report failure with a bool and an out-parameter. You propose std::expected instead. What can go wrong in such a build, and which rules and tooling do you put in place?
- What it tests
- What std::expected does without exceptions, value() that aborts, making every ignored result visible with every compiler, and migrating a large code base in safe steps.
- First step
- Start from the build mode: with -fno-exceptions, value() on an error cannot throw, so it ends the game. The rules must forbid unchecked value() and make every ignored result visible, and the change must go one subsystem at a time, with the old functions kept until their callers have moved.
- Follow-ups
-
- How do you stop value() from being called without a check?
- How do you make an ignored result warn with every compiler you build with?
- How do you migrate 400 functions without one huge change?
From the lesson std::expected vs exceptions vs error codes
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Fresher to mid
A C library reports failure by returning -1 and leaving the reason in errno. Wrap one of its functions so that C++ callers get std::expected<T, std::error_code>, and explain which mistakes with errno your wrapper prevents.
- What it tests
- Turning errno into a std::error_code with the right category, reading errno only after a failure and at once, and keeping error state out of globals.
- First step
- Call the function and look at errno only if it returned -1, and then at once, before any other call can change it. Return std::unexpected(std::error_code(errno, std::generic_category())) in that case, and the value otherwise, without looking at errno at all.
- Follow-ups
-
- Why must the wrapper read errno before calling anything else, even a logging function?
- generic_category or system_category, and does it matter on Linux and macOS?
- How does a caller test for one particular error, such as a missing file?
From the lesson std::expected vs exceptions vs error codes
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 2 minutes
-
Fresher to mid
A crash happens only in release builds. In the code you find assert(queue.pop(job)); and assert(init_logging());. Explain the bug, how you would find every such case in a large code base, and how you would stop new ones from appearing.
- What it tests
- What NDEBUG does to assert, side effects hidden in assertion conditions, finding them with tools, and team rules that keep assertions free of work.
- First step
- With NDEBUG defined, assert(anything) expands to ((void)0), so pop and init_logging are never called in release builds: no job is taken from the queue and logging never starts. Move each call onto its own line, keep its result, and assert on the result.
- Follow-ups
-
- How do you find every assert with a side effect in a million lines of code?
- Which tool can report them automatically?
- Should a release build keep some assertions?
From the lesson Assertions, preconditions and [[assume]]
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 2 minutes
-
Mid to senior
Your image library's public Image::pixel(x, y) receives coordinates that callers compute, some from user files. Should it assert, throw, return an error or use [[assume]]? Defend a policy, including what the library's own filters do in their inner loops.
- What it tests
- Telling public preconditions from internal invariants, what checks cost in hot loops, checking once outside a loop, and offering checked and unchecked access side by side.
- First step
- Ask who can break the condition. Coordinates derived from user files can be wrong without any bug in the library, so the public function must check them in every build. The library's own filters loop over bounds they already know, so they should not pay for a check on every pixel.
- Follow-ups
-
- What does one check per pixel cost on a 4000 by 3000 image?
- How do the library's own loops get unchecked access without offering it to everyone?
- Where, if anywhere, does [[assume]] fit?
From the lesson Assertions, preconditions and [[assume]]
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Mid to senior
A pull request adds [[assume(width % 16 == 0)]] to a hot loop of an image filter and claims a 20 % speed-up. What do you check before you approve it, and what would make you reject it?
- What it tests
- What an assumption promises, the undefined behaviour of a broken one, verifying a performance claim, and pairing every assumption with a check.
- First step
- Trace where width comes from. If any caller can pass a width that is not a multiple of 16, for example from an image file, the assumption is false for those inputs and the behaviour is undefined, so the change is wrong whatever it gains.
- Follow-ups
-
- How would you verify the 20 % claim?
- How can the assumption be made safe to keep?
- What could go wrong that tests built at -O0 would never show?
From the lesson Assertions, preconditions and [[assume]]
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Mid to senior
A Linux service crashes with SIGSEGV about once a day, writes no log line, and nobody can reproduce it. Walk through how you would get from the next crash to the line of code that caused it.
- What it tests
- Making a crash that cannot be reproduced leave evidence: core files, debug information kept per build, a safe crash handler, sanitizer builds in staging, and reading what they produce.
- First step
- Make the next crash leave evidence. Enable core files for the service, keep the debug information of the exact build that runs in production, and add a signal handler that writes the signal and a backtrace with async-signal-safe calls before the process dies.
- Follow-ups
-
- The core file shows the crash inside free(). What does that tell you?
- How do you keep debug information without shipping it to every server?
- What would you run in staging while you wait for the next crash?
From the lesson Debugging crashes: backtraces and std::stacktrace
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Mid to senior
A teammate's SIGSEGV handler logs the crash with std::println and then calls std::exit(1). Sometimes the process hangs instead of exiting. Why, and how would you rewrite the handler?
- What it tests
- Async-signal safety, what allocating or locking does inside a handler, exit against _exit and re-raising the signal, and what a safe handler may do.
- First step
- Start from where the handler runs: on the crashed thread, in the middle of whatever code the signal interrupted, which may be inside malloc or an output stream. Then list what std::println and std::exit need from that code before they can finish.
- Follow-ups
-
- Which functions may a handler call, and who decides?
- Why re-raise the signal instead of calling _exit?
- How can a handler run after a stack overflow?
From the lesson Debugging crashes: backtraces and std::stacktrace
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 2 minutes
-
Mid to senior
A crash report from a release build shows main calling free() directly, but in the source main calls three of your functions before anything is freed. Explain the missing frames, and how you would get complete stacks from the next release.
- What it tests
- Inlining, tail calls and frame pointers, what debug information still records, return addresses after calls that never return, and building releases that can be diagnosed.
- First step
- The trace shows the code that ran, minus frames: the optimiser inlined the three functions into main or turned a final call into a jump, and a walk along frame pointers cannot see a function that has no frame of its own. The debug information may still describe the inlined calls.
- Follow-ups
-
- Which flags would you add to release builds, and what do they cost?
- Why can a debugger still show inlined functions?
- What does a frame at offset + 0 mean?
From the lesson Debugging crashes: backtraces and std::stacktrace
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes