Question bank — CMake build types — Debug, Release, RelWithDebInfo
Read each question, answer it out loud in a full sentence before revealing. If your answer is a bare "yes"/"no", you failed — the reasoning is the point.
Picture the whole topic first
Before the traps, fix three pictures in your head. Every question below is really asking about one of these diagrams.
Picture 1 — the three independent knobs. The claim "these three settings are orthogonal" means each one moves along its own axis and does not drag the others with it. Look at the three separate dials: you can crank -O up without touching -g, and flip -DNDEBUG without touching either. There is no wire connecting them.

Because they are separate axes, a "build type" is just one chosen point in that 3-axis space — a specific reading on each dial. Look at where the four standard types land: Debug sits at speed-low / symbols-on / asserts-on, Release at speed-high / symbols-off / asserts-off, and RelWithDebInfo deliberately picks the "both" corner (fast and symbols).

Picture 2 — the default-flag matrix. Keep this table beside you; most "which flags?" traps are just misremembering one cell of it.

Picture 3 — single-config vs multi-config, and the "empty type" trap. A single-config generator (Makefiles, plain Ninja) bakes ONE type into the cache at configure time — and if you leave CMAKE_BUILD_TYPE unset, the string is literally the empty string "", which matches no CMAKE_CXX_FLAGS_<TYPE> variable, so zero extra flags are added (silent -O0, asserts still live). A multi-config generator (Visual Studio, Xcode, Ninja Multi-Config) instead carries all types and picks one at build time with --config. Follow the two branches:

True or false — justify
Release always produces a faster binary than Debug.
-O3 while Debug is -O0, but it is not a law — for tiny loops or I/O-bound code the difference can vanish, and a mis-set empty type gives you -O0 while thinking you shipped Release.-g makes your program run slower.
-g only embeds a symbol table on disk for gdb; it adds zero runtime cost. The thing that slows/changes behaviour is a low -O level, which is a separate knob.Debug and RelWithDebInfo both contain -g, so they debug equally well.
-O2, so the optimizer reorders and eliminates variables; stepping in gdb will "jump around" and some locals show as <optimized out>.Setting CMAKE_BUILD_TYPE=Release guarantees -DNDEBUG is passed.
Release bundle — CMake appends CMAKE_CXX_FLAGS_RELEASE which is -O3 -DNDEBUG by default — but only if the generator is single-config and someone hasn't overridden that cache variable.A build type changes which .cpp files get compiled.
MinSizeRel will always be smaller and slower than Release.
-Os optimizes for size). Slower: not guaranteed — smaller code can fit the instruction cache better and sometimes runs faster, so "size vs speed" is a trend, not a rule.Left with no build type set, CMake defaults to Debug.
CMAKE_BUILD_TYPE means no -O/-g/-DNDEBUG flags at all, behaving like -O0 with asserts still active.Spot the error
"I passed -DCMAKE_BUILD_TYPE=Debug so Visual Studio will build Debug."
CMAKE_BUILD_TYPE and choose the config at build time via cmake --build build --config Debug."-DNDEBUG turns off my own if(debug_mode) logging."
NDEBUG only gates the standard assert() macro (and code you wrote inside #ifndef NDEBUG); your own runtime if(...) branches are untouched. See assert and the NDEBUG macro."My Release binary is slow, so -O3 must be broken."
CMAKE_BUILD_TYPE was left empty, so it was really -O0; check grep BUILD_TYPE build/CMakeCache.txt before blaming the optimizer."Release strips -g, therefore a Release core dump can never be symbolized."
-g is right, but the conclusion is too strong — you can either ship RelWithDebInfo, or keep a separate .debug symbol file and match it to the stripped binary later."I added -fsanitize=address to my flags but forgot to change the build type; that's fine."
-O1 -g and no aggressive optimization or -DNDEBUG; pairing it with a Release bundle can hide bugs and disable asserts, defeating the point."To make a custom ASAN type I only need to name it in --build --config ASAN."
CMAKE_CXX_FLAGS_ASAN; a build type is just a lookup of CMAKE_CXX_FLAGS_<TYPE>, and an undefined one contributes no flags.Why questions
Why are -g and -O called "orthogonal"?
-g asks "can I map machine code back to source?" while -O asks "how hard should the compiler optimize?" You can turn each on or off without touching the other, which is exactly why RelWithDebInfo can have both.Why does RelWithDebInfo use -O2 instead of -O3?
-O2 keeps most of the speed while producing debug info that still lines up reasonably with the source; -O3's more aggressive transforms would make backtraces and stepping even more confusing for the profiling/crash-dump use case.Why does shipping code define NDEBUG?
assert() checks cost runtime and a shipped binary shouldn't abort on a failed internal invariant in front of users; -DNDEBUG compiles those checks out entirely for speed.Why is the effective flag line CMAKE_CXX_FLAGS + CMAKE_CXX_FLAGS_<TYPE> and not just one of them?
CMAKE_CXX_FLAGS holds flags you always want (warnings, standard version) regardless of type, and the per-type variable adds the profile on top — separating "always" from "this profile" so you don't repeat yourself for every type.Why prefer RelWithDebInfo over Debug for reproducing a production performance bug?
-O0 runs a fundamentally different (slower, un-optimized) program, so timing bugs may not reproduce; RelWithDebInfo runs code close to your real -O2 workload while still carrying symbols.Why doesn't setting CMAKE_BUILD_TYPE help with a Ninja Multi-Config generator?
--config; there is no single "the type" to store, so the variable is simply ignored.Edge cases
If you set CMAKE_BUILD_TYPE=release (lowercase), what happens?
CMAKE_CXX_FLAGS_RELEASE won't match release and you get no optimization flags — the same silent trap as leaving it empty.Can a target still contain assert() behaviour in a Release build?
NDEBUG was somehow not defined for that translation unit (e.g., custom flags, or an assert-like macro you rolled yourself); the standard assert is compiled out once -DNDEBUG is present.What if you pass both -O0 in CMAKE_CXX_FLAGS and pick Release (-O3)?
-O0 ... -O3, and for GCC/Clang the last -O wins, so you'd effectively get -O3 — but relying on ordering like this is fragile and worth avoiding.In a multi-config build tree, is there any use for CMAKE_BUILD_TYPE?
--config flag at build time.Does MinSizeRel include -g?
-Os -DNDEBUG), because its whole purpose is the smallest possible firmware/embedded binary, and symbols add size.What is the smallest change to get gdb-usable symbols in a Release-speed binary without redefining types?
RelWithDebInfo type (-O2 -g -DNDEBUG); no custom cache variable needed, near-Release speed with symbols intact.Recall One-line self-test
Empty CMAKE_BUILD_TYPE on a single-config generator behaves like which type? ::: Like -O0 with no -DNDEBUG — so asserts are active but nothing is optimized, which is not the same as Debug (Debug explicitly adds -g).