CMake build types — Debug, Release, RelWithDebInfo
WHAT is a build type?
The variable that controls everything for single-config generators (Makefiles, Ninja):
cmake -S . -B build -DCMAKE_BUILD_TYPE=ReleaseThe per-type flag variables that CMake actually appends:
| Type | Typical GCC/Clang flags (CXX_FLAGS_<TYPE>) |
|---|---|
Debug |
-g |
Release |
-O3 -DNDEBUG |
RelWithDebInfo |
-O2 -g -DNDEBUG |
MinSizeRel |
-Os -DNDEBUG |
The final command line is
CMAKE_CXX_FLAGS(always) +CMAKE_CXX_FLAGS_<TYPE>(for the chosen type).
WHY do these flags matter?
Three knobs are being tuned:
-Ooptimization level —-O0(none) up to-O3(aggressive). Higher = faster runtime, slower compile, harder to debug (variables get reordered/eliminated).-gdebug info — embeds a symbol table mapping machine instructions back to source lines/variables sogdbcan show them. Costs disk size, zero runtime cost.-DNDEBUG— defines theNDEBUGmacro, which makes the C/C++ standardassert()expand to nothing. Removes safety checks for speed in shipping builds.

HOW the four types compare
Worked example Worked example 1: Configuring a Release build
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
cmake --build buildWhy -S . -B build? Out-of-source build keeps generated junk out of your repo.
Why does the compiler line end up with -O3 -DNDEBUG? Because CMake appended CMAKE_CXX_FLAGS_RELEASE. The asserts in your code now vanish → faster, but no runtime safety net.
Worked example Worked example 2: I get a slow binary even with -O3, why?
Symptom: built Release but performance is bad.
Check: cmake -B build -DCMAKE_BUILD_TYPE=Release && grep BUILD_TYPE build/CMakeCache.txt.
Why this step? If CMAKE_BUILD_TYPE is empty (the default when you forget it!), CMake applies no optimization flags at all — your "Release-looking" build is actually -O0. The fix is to always set the type explicitly.
Worked example Worked example 3: production crash with a usable backtrace
You want a fast binary but readable stack traces when it crashes in the field.
cmake -S . -B build -DCMAKE_BUILD_TYPE=RelWithDebInfoWhy not Release? Release strips -g, so a core dump shows ?? instead of function names/lines. Why not Debug? -O0 is too slow for production and behaves differently from your real workload. RelWithDebInfo = -O2 -g: nearly Release speed, but symbols survive.
Worked example Worked example 4: defining your own type
set(CMAKE_CXX_FLAGS_ASAN "-O1 -g -fsanitize=address" CACHE STRING "" FORCE)
# then: cmake -DCMAKE_BUILD_TYPE=ASAN ...Why this works: build types are just variable lookups of the form CMAKE_CXX_FLAGS_<TYPE>. Define the variable and the name becomes usable.
Multi-config generators (a subtle trap)
Flashcards
What variable selects the build type for single-config generators?
CMAKE_BUILD_TYPEName the four standard CMake build types.
Which flags does Release inject (GCC/Clang default)?
-O3 -DNDEBUGWhich flags does RelWithDebInfo inject?
-O2 -g -DNDEBUGWhich flags does Debug inject?
-g (effectively -O0, no NDEBUG)What does -DNDEBUG actually do?
assert() expands to nothingAre -g and -O mutually exclusive?
What happens if CMAKE_BUILD_TYPE is left empty?
-O0)How do multi-config generators pick the type?
cmake --build . --config <Type>, not via CMAKE_BUILD_TYPEFormula for the effective flags of type T?
CMAKE_CXX_FLAGS + CMAKE_CXX_FLAGS_<T>Best type for a fast production binary that still gives readable stack traces?
What does -g cost at runtime?
Recall Feynman: explain to a 12-year-old
Imagine you wrote an essay. Debug is the messy draft with all your notes in the margins (-g) so you can find your mistakes easily, but it's slow to read. Release is the clean printed final copy: fast to read, no margins, no notes (-O3 -DNDEBUG). RelWithDebInfo is the clean copy that also keeps a tiny key in the back telling you which page each idea came from — almost as fast, but if something goes wrong you can still trace it. The computer code is the same essay; you're just choosing how much "fast" vs "explainable" you want.
Concept Map
Hinglish (regional understanding)
Intuition Hinglish mein samjho
Dekho, CMake mein "build type" koi magic nahi hai — ye sirf compiler ko diye jaane wale flags ka ek naam hai. Jab tum -DCMAKE_BUILD_TYPE=Release likhte ho, CMake bas -O3 -DNDEBUG add kar deta hai. Aur agar Debug set karo, to -g lagta hai (optimization off). Code wahi rehta hai, sirf flags badalte hain. Isliye poora topic itna hi hai: "kaunsa type kaunse flags daalta hai aur kyun".
Teen cheezein samajh lo: -O matlab speed (jitna upar, utna fast par debug karna mushkil), -g matlab debug symbols (gdb ko pata chalta hai kaunsi line, runtime pe koi cost nahi, sirf size badhta hai), aur -DNDEBUG matlab assert() band ho jaata hai (shipping ke liye fast). Sabse important baat — -g aur -O ek dusre ke dushman nahi hain. Dono saath ho sakte hain, aur wahi to RelWithDebInfo hai: -O2 -g, yaani Release jitna fast par crash hone par proper stack trace bhi milega. Production ke liye ye sabse pasandida hai.
Ek common galti: agar tum CMAKE_BUILD_TYPE set karna bhool jaao, to CMake koi optimization flag nahi daalta — tumhara binary -O0 jaisa slow chalega, bhale tum soch rahe ho "Release hai". Isliye type hamesha explicitly do. Aur dhyan rakho: Visual Studio / Xcode / Ninja Multi-Config jaise generators CMAKE_BUILD_TYPE ko ignore karte hain — wahan build time pe cmake --build build --config Release se type choose karna padta hai.