C++ Module 17
Modules and coroutines interview questions
Interview questions on modules and coroutines: 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
What does export do in a C++ module, and what does an importer not get?
Answered in the lesson C++20 modules: export, import and partitions
-
Fresher to mid
What is the difference between import std; and import std.compat;, and what does neither of them provide?
Answered in the lesson import std in C++23: which toolchains support it
-
Fresher to mid
What makes a C++ function a coroutine, and what do promise_type and coroutine_handle do?
Answered in the lesson C++ coroutines: co_await, co_yield and co_return
-
Fresher to mid
What is a generator in C++, and when would you return one instead of a std::vector?
Answered in the lesson Generators: std::generator and writing your own
-
Fresher to mid
What do await_ready, await_suspend and await_resume do, and in which order does co_await call them?
Answered in the lesson Async coroutines: tasks, awaitables, event loops
Questions with model answers
What each question tests, the first step of a good answer and the follow-ups to expect.
-
Mid to senior
Your team turns a widely used utility header into a module. Afterwards, files that used the header's MAX_RETRIES macro or its detail::parse helper stop compiling, and the CI job on Macs fails at CMake's generate step. Explain each failure and how you would fix it without reverting the change.
- What it tests
- Whether macros and non-exported names are known not to cross an import, how to expose what consumers really need, and which compilers and generators CMake can build modules with.
- First step
- Separate the three failures. A macro never leaves a named module, so MAX_RETRIES needs a new home. detail::parse is not exported, so it has module linkage. The Mac job is a toolchain problem: CMake cannot scan imports with Apple clang, so the module build needs another compiler there.
- Follow-ups
-
- Should detail::parse simply be exported?
- How do consumers that must keep the header keep working during the change?
- Which compiler would you use for the Mac job, and what does it cost?
From the lesson C++20 modules: export, import and partitions
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 4 minutes
-
Mid to senior
A hand-written Makefile builds a library with three module interface units and a dozen importers. With make -j8 the build fails about one time in ten with "module not found", and always passes with make -j1. Explain the failure and how you would make the build correct.
- What it tests
- Understanding that imports impose a compile order, why parallel builds expose missing edges, and how dependency scanning in CMake with Ninja solves it.
- First step
- The error comes from an importer compiled before the module file it reads exists. With -j1 the rule order happens to work; with -j8 independent rules run together. The Makefile is missing edges from each importer to the module files of what it imports.
- Follow-ups
-
- Why does -j1 hide the bug?
- How does CMake know the order without being told?
- What does a change to an implementation unit force to rebuild, compared with a change to an interface?
From the lesson C++20 modules: export, import and partitions
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 4 minutes
-
Mid to senior
Your team adopts import std;. Linux developers using GCC 16 are fine, but every Mac developer gets "module 'std' not found", although a build check shows that __cpp_lib_modules is defined. Explain what is going on and how you would set up the Macs.
- What it tests
- Knowing that a std module is built from sources the library ships, that Apple's toolchain ships none, why the feature-test macro misleads, and the workable Mac toolchains.
- First step
- Separate what the library claims from what the toolchain can build. __cpp_lib_modules comes from the library's version header; import std; needs the std module's sources, which Apple's Command Line Tools do not ship. Then pick a Mac toolchain that ships them: GCC 16 or an LLVM release.
- Follow-ups
-
- Why is the feature-test macro defined at all?
- What changes in the build when the Macs move to Homebrew's LLVM?
- How would you keep one source tree working for developers who stay on Apple clang?
From the lesson import std in C++23: which toolchains support it
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 4 minutes
-
Senior to staff
A build script compiles the std module once, with -std=c++23 -O2, and every target imports it. One target moves to C++26 and another adds -D_GLIBCXX_ASSERTIONS for hardening. What breaks loudly, what breaks silently, and how should std module builds be organised?
- What it tests
- Which differences between a module and its importers compilers check when they load it, what a macro on the importer can still change, and how to organise std module builds.
- First step
- Split the options into two groups: those that define the language dialect, such as -std, and macros, such as -D_GLIBCXX_ASSERTIONS. For each group, ask whether the compiler compares it with the module file's options, and what the std module's code already contains when the importer is compiled.
- Follow-ups
-
- How would you prove that the hardened target is not hardened?
- Does -O0 against -O2 matter for the std module?
- What does CMake do differently from the build script?
From the lesson import std in C++23: which toolchains support it
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 4 minutes
-
Mid to senior
Your project has 300 source files and builds with GCC 16 on Linux and with Clang and libc++ on macOS. A colleague proposes converting every file to import std;. How would you estimate whether it is worth it, and what would you measure first?
- What it tests
- Turning the one-time cost of the std module and the per-file saving into a break-even estimate, measuring CPU time properly, and knowing the saving differs by toolchain.
- First step
- Measure, do not guess: take a few representative files, compile each to an object file with #include and with import std;, and record CPU time over several runs on each toolchain, plus the one-time cost of building the std module. Then compute saved time per file times file count against that cost.
- Follow-ups
-
- Why CPU time rather than the time on the clock?
- What does GCC's include translation change in the comparison?
- Which files gain least, and why?
From the lesson import std in C++23: which toolchains support it
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Senior to staff
You maintain a 500-file C++ library used by other teams on GCC, Clang and MSVC, some with older compilers and plain Makefiles. You are asked to move it to modules. Plan the migration: what you ship first, what you must never break, and how you know each step worked.
- What it tests
- Planning a gradual migration around consumers who cannot import yet, choosing ABI-preserving styles, and defining checks that prove each step changed nothing.
- First step
- Start from the consumers, not the code. Some can never import yet, so the headers and the ABI must keep working throughout. That points to wrappers first: a module per library that re-exports the existing headers, shipped beside them, with a CI build that compiles both kinds of consumer.
- Follow-ups
-
- When, if ever, would you attach the declarations to the modules?
- How do you ship module files to users on different compilers?
- What do you measure to decide whether the migration was worth it?
From the lesson Migrating a C++ project to modules
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Mid to senior
After a teammate moved a library's declarations into its module, the link fails with undefined symbols for functions that clearly exist. nm -C on the library shows textutil::split@textutil. Explain the failure and give two ways to fix it.
- What it tests
- Understanding module attachment and its effect on linkage names, how to find the files still using the old declarations, and the extern "C++" alternative.
- First step
- Read the two names the linker is matching: the library defines split attached to the module textutil, which is what @textutil means, while some object file was compiled from the old header and asks for the global-module split. Find which objects reference the plain name, with nm on each.
- Follow-ups
-
- Why does the linker's message show the same name twice?
- When is extern "C++" the better fix?
- How do you stop this from reaching the main branch again?
From the lesson Migrating a C++ project to modules
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Mid to senior
Users of your library select a faster code path with -DTEXTUTIL_FAST_SPLIT, which the header's inline code tests, and they check TEXTUTIL_VERSION in #if. How do you keep both working once they import the module instead of including the header?
- What it tests
- Knowing that importers' macros cannot reach a module's code, separating configuration macros from values, and moving build-time configuration to the build of the module itself.
- First step
- Treat the two macros differently. TEXTUTIL_VERSION flows from the library to the user, and #if can read it only from a header, so it moves to a small config header, with an exported constant for code. TEXTUTIL_FAST_SPLIT flows from the user into the library, and an importer's -D flags never reach code inside a module.
- Follow-ups
-
- Where does the -DTEXTUTIL_FAST_SPLIT setting have to go instead?
- What if two parts of one program want different settings?
- Could a template or a consteval switch replace the macro?
From the lesson Migrating a C++ project to modules
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Mid to senior
A lazy coroutine task, Task load(const std::string& path), passes every unit test but crashes now and then in production, where it is created with a path built inside the calling function and run later by a scheduler. What is going on, and how do you fix it and keep it fixed?
- What it tests
- Understanding what the frame keeps for each parameter, why tests miss the bug, how sanitizers expose it, and which interface rules prevent it.
- First step
- Follow the string's lifetime from the call to the moment the scheduler first resumes the task. Ask two questions: what does the frame keep for a parameter of reference type, and when does a lazy task's body run compared with the end of the calling function?
- Follow-ups
-
- Why do the unit tests pass?
- When is a reference parameter to a coroutine acceptable?
- How would you catch this class of bug in CI?
From the lesson C++ coroutines: co_await, co_yield and co_return
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Mid to senior
In a code review you see: auto task = [this, config]() -> Task { co_await fetch(); apply(config); }(); and the task is stored and run later. What is wrong with it, even though config is captured by value, and how would you rewrite it?
- What it tests
- Knowing where a coroutine lambda's captures live while it is suspended, why capturing by value does not help, the rewrite the C++ Core Guidelines recommend, and the lifetime of the captured object.
- First step
- Ask where config lives while the coroutine is suspended, and who owns that storage. The lambda is called on a temporary closure object in the same statement that stores the task; compare that object's lifetime with the moment the task is finally run.
- Follow-ups
-
- Why does capturing by value not help?
- What about the captured this pointer?
- Can the compiler or a sanitizer catch it?
From the lesson C++ coroutines: co_await, co_yield and co_return
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Senior to staff
Design the return type of a coroutine used for background jobs: who owns the frame, when the body starts, how errors reach the caller and what happens if the job object is dropped. Walk through the promise hooks you would write and why.
- What it tests
- Turning the promise hooks into ownership and error-handling decisions: lazy start, final suspension, a move-only owner, exception transport and safe destruction.
- First step
- Start from ownership: which object owns the frame, and how many times may it be destroyed? Then choose what initial_suspend and final_suspend return so that nothing runs before that owner exists and nothing is freed while the owner may still read the result.
- Follow-ups
-
- What changes if the job must start immediately?
- How should a dropped, still-suspended job behave?
- Where would cancellation fit into these hooks?
From the lesson C++ coroutines: co_await, co_yield and co_return
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Mid to senior
A colleague's code stores pointers to the strings a generator yields in a vector inside the loop, then prints them after the loop. It printed the right names in a few tests and garbage in others. Explain what happens and how you would fix and catch it.
- What it tests
- Knowing how long a yielded value lives, that the frame itself ends with the generator, how undefined behaviour hides in tests, and the fixes.
- First step
- Ask where each string lives and when it ends. A yielded value stays valid only until the generator resumes, and the loop resumes it on every step; after the loop, the generator object itself is gone. So check what a stored pointer refers to at the moment the code after the loop reads it.
- Follow-ups
-
- Why did some tests print the right names?
- Which fix would you choose, and what does it cost?
- Does std::generator<const std::string&> change anything?
From the lesson Generators: std::generator and writing your own
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Senior to staff
Your code base must build with GCC 16 and with Apple clang, whose library has no <generator>. How would you provide generators, and what must a hand-written Generator<T> get right in its promise, its iterator and its ownership to be safe and work with std::views?
- What it tests
- Designing a portable generator type: the promise's storage of values, exception transport, the iterator and sentinel concepts, view behaviour and move-only ownership, plus when to switch to std::generator.
- First step
- Choose the strategy first: one project-wide generator type, used everywhere, so code does not depend on which library a compiler ships. Then list the parts: a promise that records the current value and any exception, an input iterator with a sentinel, and an owner that frees the frame exactly once.
- Follow-ups
-
- Should the promise copy each yielded value?
- What makes the type usable on the left of a view pipeline?
- When would you replace it with std::generator?
From the lesson Generators: std::generator and writing your own
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Senior to staff
A tree traversal written as a recursive generator, where each node's generator loops over its children's generators and re-yields their values, is slow on large trees and crashes on very deep ones. Explain both problems with numbers and give two fixes.
- What it tests
- Estimating the cost of re-yielding through every level, understanding nested resumes and the stack, and knowing elements_of and the explicit-stack rewrite.
- First step
- Count the work for one value: through how many co_yield statements does a value produced at depth d pass before the consumer sees it? Add that up over the tree. Then consider what the chain of re-yielding generators does to the call stack while the innermost one runs.
- Follow-ups
-
- How much work is it for a path-shaped tree of 1,000 nodes?
- Why does the deep tree crash, and at about what depth?
- Which fix keeps the code recursive?
From the lesson Generators: std::generator and writing your own
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Senior to staff
A coroutine task library works in release builds, but debug builds crash with a stack overflow when a request passes through a few hundred thousand chained co_awaits, for example a long recursive retry. The library resumes the awaiting coroutine from the final awaiter. What is happening, and how do you fix it?
- What it tests
- Understanding nested resumption, symmetric transfer and how compilers implement it, and why optimisation level changes the outcome on some compilers.
- First step
- Look at how a finishing task hands control back: if its final awaiter calls continuation.resume(), each finished task in the chain is still on the stack when the next one runs. Then compare that with an await_suspend that returns the continuation's handle, and with what each optimisation level does to that call.
- Follow-ups
-
- Why does the release build survive?
- How would you measure the stack used per task?
- What should the debug build flags be?
From the lesson Async coroutines: tasks, awaitables, event loops
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Senior to staff
A when_all helper for coroutine tasks works in every test, but production reports a use-after-free inside the event loop, only on requests where one of the children fails. Diagnose the problem and redesign when_all.
- What it tests
- Recognising a frame destroyed while the loop still holds its handle, structured concurrency, the language rules a fix must respect, and cancellation as the faster fix.
- First step
- Follow the failing request: when the first child throws, what happens to when_all's frame and to the other children it owns? Then ask who still holds a handle to one of those children, and when it will be resumed.
- Follow-ups
-
- Why do the tests pass?
- How would cancellation change the design?
- Which error should when_all report when both children fail?
From the lesson Async coroutines: tasks, awaitables, event loops
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Senior to staff
Design a single-threaded event loop for coroutines that wait on timers and on sockets becoming readable. What does the loop store, how does it decide what to resume next, and how do you handle cancellation and shutdown?
- What it tests
- Turning awaitables into a loop: a timer queue, readiness polling, resumption order, fairness, cancellation and clean shutdown without dangling frames.
- First step
- Start from what an awaitable hands the loop in await_suspend: a coroutine handle and the event it waits for. The loop then needs one structure per kind of event, ordered timers and a set of watched sockets, and one blocking call that waits for the earliest of them.
- Follow-ups
-
- How do you test the loop deterministically?
- What happens to suspended coroutines at shutdown?
- How would you add work from another thread?
From the lesson Async coroutines: tasks, awaitables, event loops
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes