Variables
Types, lvalue/rvalue, const, constexpr, and initialization forms.
47 questions
JuniorTheoryVery commonWhat does auto do? Auto return type, auto parameters (C++20).
What does auto do? Auto return type, auto parameters (C++20).
auto deduces the type from the initialiser by template-argument-deduction rules; top-level const/& are dropped — use const auto& to keep them. C++14 added auto return type. decltype(auto) keeps the exact type. C++20 auto parameters make abbreviated templates.
Common mistakes
- ✗Writing
auto x = vec.begin();then modifyingvec—xis now a dangling iterator - ✗Using
autofor proxy types likestd::vector<bool>::reference— gives unexpected behaviour when assigning - ✗Expecting
autoto deduce a reference:auto x = obj.getMember();copies even ifgetMember()returnsT&
Follow-up questions
- →What is the difference between
auto,auto&,const auto&, andauto&&? - →When would you prefer
decltype(auto)return type over plainauto?
JuniorTheoryVery commonWhat is enum? What is the difference between enum and enum class?
What is enum? What is the difference between enum and enum class?
enum (unscoped) leaks enumerators into the enclosing scope and converts implicitly to int. enum class (scoped, C++11) keeps them in their own scope and does not convert implicitly — static_cast is required. Both allow a custom underlying type.
Common mistakes
- ✗Using unscoped
enummembers in a different scope and getting name clashes with other enums or variables - ✗Comparing values of two different
enum classtypes — not allowed without casting - ✗Forgetting that
enum classvalues are not automatically printable — you need ato_stringoroperator<<
Follow-up questions
- →How do you iterate over all values of an
enum class? - →What is the default underlying type of an
enum classwithout an explicit specification?
JuniorTheoryVery commonWhat are the uses of extern?
What are the uses of extern?
extern has two uses: (1) declaring a variable or function defined in another TU so the linker resolves its address; (2) extern "C" suppresses name mangling for C interop. Without extern, a namespace-scope variable declaration is also a definition, breaking ODR across TUs.
Common mistakes
- ✗Confusing
externdeclaration with a definition —extern int x;declares;int x;at global scope defines - ✗Forgetting to provide exactly one definition when using
extern— linker error if zero or multiple definitions exist - ✗Applying
extern "C"to a C++ class or template — cannot be done; only free functions and POD variables
Follow-up questions
- →How do
inlinevariables (C++17) replace the need for a separateexterndeclaration in headers? - →What is the difference between
extern const int x;andconst int x;at file scope?
JuniorTheoryVery commonWhat are the fundamental and built-in types in C++?
What are the fundamental and built-in types in C++?
Fundamental types: void, bool, characters (char, wchar_t, char8/16/32_t), integers (short/int/long/long long, signed/unsigned), floating-point (float, double, long double). Sizes are implementation-defined; <cstdint> gives fixed-width aliases like int32_t.
Common mistakes
- ✗Assuming
intis always 32 bits — guaranteed to be at least 16 bits; useint32_twhen size matters - ✗Using
chararithmetic without casting —charmay be signed or unsigned depending on the platform - ✗Mixing signed and unsigned integer arithmetic — implicit conversion can produce surprising results
Follow-up questions
- →What is the difference between
int8_t,int_fast8_t, andint_least8_t? - →When would you use
long doubleoverdouble?
JuniorTheoryVery commonExplain lvalue, rvalue, and xvalue. How does value category affect overload resolution?
Explain lvalue, rvalue, and xvalue. How does value category affect overload resolution?
An lvalue is a named, addressable expression that outlives the expression. An rvalue is a temporary without a stable address. An xvalue (expiring) is a named object cast to rvalue ref via std::move. Overloads prefer T&& over const T& for rvalues, enabling moves.
Common mistakes
- ✗Confusing rvalue with 'right-hand side of assignment' — it is about value category, not position
- ✗Thinking that a named rvalue reference (T&&) is itself an rvalue inside the function body — it is an lvalue
- ✗Using std::move on a return value that NRVO would have already elided
Follow-up questions
- →What is the difference between a prvalue and an xvalue?
- →Why does std::move return T&& and what does that actually do?
JuniorTheoryVery commonWhat is a const pointer, a pointer to const, and a reference? How do const int and int const differ?
What is a const pointer, a pointer to const, and a reference? How do const int and int const differ?
const int and int const are the same type. With pointers: const T* rebinds but pointee is read-only; T* const is fixed but pointee is mutable; const T* const locks both. A reference is a non-null alias that cannot be rebound. Pointers usually take a machine word.
Common mistakes
- ✗Claiming
const intandint constare different types — they are the same - ✗Reading
const T*as 'const pointer to T' — the const applies to what is pointed at, not the pointer itself - ✗Binding a non-const reference to a temporary — not allowed; use a
constreference orauto&&(C++11)
Follow-up questions
- →Why are
const intandint constequivalent, butconst int*andint* constare not? - →What happens to a const reference when the temporary it is bound to would normally expire?
JuniorTheoryVery commonWhat are the uses of volatile? When is it not enough?
What are the uses of volatile? When is it not enough?
volatile tells the compiler the variable may change outside program control (hardware, signal handlers, setjmp); it must not cache or reorder reads/writes. NOT a synchronisation primitive: no cross-thread ordering. Use std::atomic for thread safety.
Common mistakes
- ✗Using
volatilefor inter-thread communication — the standard gives no ordering guarantee; usestd::atomic - ✗Thinking
volatileprevents compiler optimisations globally — it only disables caching for the specific variable - ✗Confusing
volatilewithconst volatileon memory-mapped I/O — read-only hardware registers should beconst volatile
Follow-up questions
- →Can a
volatilevariable be atomic? When would you combine both qualifiers? - →In what embedded/kernel contexts is
volatilestill the correct tool?
MiddleTheoryVery commonWhat is the difference between const and constexpr? When should you use each?
What is the difference between const and constexpr? When should you use each?
const means the value cannot be modified through that reference, but it need not be known at compile time. constexpr means it must be computable at compile time and is implicitly const. Use constexpr for compile-time constants; const for runtime-immutable values.
Common mistakes
- ✗Expecting const local variables to always be compile-time constants — they may not be
- ✗Using constexpr on a function that calls a non-constexpr function — fails to compile in constexpr context
- ✗Confusing constexpr and consteval (C++20): consteval forces compile-time evaluation; constexpr is evaluated at compile time when possible
Follow-up questions
- →What is consteval and how does it differ from constexpr?
- →Can a constexpr function call non-constexpr functions?
MiddleTheoryVery commonWhat is a reference to a temporary? How do you extend its lifetime?
What is a reference to a temporary? How do you extend its lifetime?
A temporary lives until the end of the full expression. Binding const T& or T&& extends its lifetime to the reference's lifetime. Extension does not propagate through function params, member init, or std::move. Returning a local by const& is UB.
Common mistakes
- ✗Passing a temporary to a function taking
const T&and then storing that reference — it's a dangling ref after the call returns - ✗Thinking
auto&& x = expr;always extends lifetime — it does only when the temporary is bound directly to the ref, not nested - ✗Using a const reference to hold a base-class view of a temporary derived object — lifetime is extended, but slicing is not
Follow-up questions
- →What happens if you bind a temporary to a member reference in a constructor initialiser list?
- →How does
std::reference_wrapperdiffer from a plain reference in terms of lifetime?
JuniorTheoryCommonHow are floating-point numbers represented and why is direct equality comparison dangerous?
How are floating-point numbers represented and why is direct equality comparison dangerous?
Most float/double use IEEE 754: sign bit, biased exponent, significand. Many decimal fractions are not exactly representable in binary, so arithmetic accumulates rounding error; == is unreliable.
Common mistakes
- ✗Expecting 0.1 + 0.2 == 0.3
- ✗Using one universal epsilon for values with very different magnitudes
- ✗Ignoring NaN and infinity in parsing or math code
Follow-up questions
- →What is the difference between absolute and relative epsilon?
- →How does NaN behave in comparisons?
JuniorTheoryCommonWhat is variable initialisation inside if?
What is variable initialisation inside if?
C++17 allows an init-statement in if/switch: if (auto it = map.find(key); it != map.end()) { ... }. The init-variable is scoped to the whole if/else block. It avoids polluting the enclosing scope and pairs well with RAII guards.
Common mistakes
- ✗Trying to use the init-statement variable after the entire if/else block — it is out of scope
- ✗Forgetting the semicolon separator:
if (init; condition)— missing the;is a compile error - ✗Using this in pre-C++17 compilers without a compatibility guard
Follow-up questions
- →How does
ifinit-statement interact with structured bindings in C++17? - →Can you use a
try/catchas an init-statement? Why not?
JuniorTheoryCommonWhat is the difference between int8_t and char? When does signedness matter?
What is the difference between int8_t and char? When does signedness matter?
int8_t is an exact-width signed 8-bit type guaranteed by the standard. char may be signed or unsigned depending on the platform and compiler flags.
Common mistakes
- ✗Assuming char is always signed — MSVC and many ARM targets default to unsigned char
- ✗Using char for arithmetic then comparing against -1 — silent sign-extension bugs
- ✗Treating char and uint8_t as freely interchangeable
Follow-up questions
- →When is std::byte the right choice?
- →What is -funsigned-char and when would you set it?
JuniorTheoryCommonWhat is a namespace? Anonymous namespace? Nested namespace access.
What is a namespace? Anonymous namespace? Nested namespace access.
A namespace groups related names to avoid collisions. An anonymous namespace gives contents internal linkage — like static at file scope but also for types/classes. Nested ones use :: or using. C++17 allows namespace A::B::C { ... }.
Common mistakes
- ✗Placing
using namespaceat file scope in a header — pollutes all includers - ✗Confusing anonymous namespace with a named namespace with a single-TU lifetime
- ✗Not knowing that
namespacecan be reopened and extended in multiple files
Follow-up questions
- →What is an inline namespace and how is it used for ABI versioning?
- →How do argument-dependent lookup (ADL) rules interact with namespaces?
JuniorTheoryCommonHow do you get the min/max value of a type in C++?
How do you get the min/max value of a type in C++?
Use std::numeric_limits<T> from <limits>: ::max(), ::lowest(). For integers min() is the most negative; for floats min() is the smallest positive normalised value — use lowest() for the most negative float. C macros like INT_MAX work but are not generic.
Common mistakes
- ✗Using
std::numeric_limits<float>::min()to get the most negative float — it is the smallest positive value; uselowest() - ✗Not including
<limits>before usingstd::numeric_limits - ✗Using numeric_limits with non-specialized types — the default specialization returns zeros, which silently does nothing useful
Follow-up questions
- →What does
std::numeric_limits<T>::is_integertell you? - →How do you check at compile time whether a type can represent a specific value?
JuniorTheoryCommonHow does range-for differ from a traditional for?
How does range-for differ from a traditional for?
Range-for is sugar over begin()/end(): it advances an iterator until end(). Works for any type providing begin/end (member or free via ADL). No built-in index without std::views::enumerate (C++23); mutating the container invalidates the iterator (UB).
Common mistakes
- ✗Using
autoinstead ofauto&— copies each element, slow for large objects - ✗Using
auto&on a temporary range expression — the range is destroyed before iteration ends (dangle) - ✗Erasing/inserting elements inside a range-for — invalidates the underlying iterator
Follow-up questions
- →How do you make a custom class compatible with range-for?
- →What does
for (auto&& x : range)buy you overfor (auto& x : range)?
JuniorTheoryCommonHow does static affect global vs local variables?
How does static affect global vs local variables?
On a local, static gives static storage duration: initialised once on first pass and lives until program exit. On a global or function, it changes external linkage to internal — invisible outside the TU. Two unrelated uses of the same keyword.
Common mistakes
- ✗Thinking static local variables are thread-safe before C++11 — since C++11 initialisation is guaranteed thread-safe, but subsequent access is not
- ✗Using
staticon a global variable to 'save memory' — it changes linkage, not lifetime or size - ✗Forgetting that a static local's destructor runs in reverse initialisation order at program exit — be careful with inter-dependency
Follow-up questions
- →What is the static initialisation order fiasco and how do static locals help avoid it?
- →What happens if a
staticlocal variable's constructor throws?
JuniorTheoryCommonHow do you determine the size of a struct in C++?
How do you determine the size of a struct in C++?
sizeof(T) returns the size in bytes including padding. The compiler aligns each field to its natural alignment and adds trailing padding so array elements stay aligned; total is a multiple of the struct's largest alignment. Use offsetof or #pragma pack to inspect or override layout.
Common mistakes
- ✗Assuming
sizeof(S)equals the sum of member sizes — padding can add several bytes - ✗Reordering members without thinking about padding — put larger members first to minimise wasted space
- ✗Using
sizeofon a pointer to a struct — returns the pointer size, not the struct size
Follow-up questions
- →How does
alignaslet you override a type's default alignment? - →What is the difference between
sizeofandalignof?
MiddleTheoryCommonWhat is an aggregate type and how does aggregate initialisation work?
What is an aggregate type and how does aggregate initialisation work?
An aggregate is a class with no user-provided constructors, no private/protected data members, and no virtual functions or virtual bases; T x{a,b,c}; initialises members in declaration order with value-initialisation for unlisted ones.
Common mistakes
- ✗Adding a constructor to a struct used for aggregate init — breaks
T{a, b} - ✗Relying on member declaration order — adding a member breaks all
T{}sites - ✗Mixing designated and positional initialisers — not allowed in C++20
Follow-up questions
- →How did C++17 change aggregate rules with public bases?
- →When is class template argument deduction (CTAD) supported for aggregates?
MiddleTheoryCommonWhat are the type deduction rules for auto? When can it copy unexpectedly?
What are the type deduction rules for auto? When can it copy unexpectedly?
auto follows template-type-deduction rules: refs and top-level const are stripped; arrays and functions decay to pointers. So auto x = v[0]; copies even if it looks lightweight. decltype(auto) preserves the exact declared type including references.
Common mistakes
- ✗Writing
auto x = heavyObject.get();— strips the reference returned byget()and makes a full copy - ✗Using
autowithstd::vector<bool>— deducesstd::vector<bool>::reference, a proxy, notbool - ✗Expecting
autoto preserve the cv-qualification of the expression — top-level const is always stripped
Follow-up questions
- →What are the three flavours of
autodeduction (value, reference, forwarding reference) and their strip rules? - →When is
decltype(auto)the right choice for a return type?
MiddleTheoryCommonWhat is const correctness and why does it matter?
What is const correctness and why does it matter?
Const correctness means marking every variable, parameter, and method that doesn't modify state as const. It catches accidental mutations, allows zero-cost const& parameters, and only const-qualified methods may be called on const objects.
Common mistakes
- ✗Forgetting
conston getters — then they cannot be called onconstobjects or throughconstreferences - ✗Casting away
constwithconst_castto work around a design flaw — the real fix is to add the correctconstoverload - ✗Not marking parameters
const T&when the function does not modify them — leads to unnecessary copies or incompatible call sites
Follow-up questions
- →How do you provide both a
constand a non-const overload of an accessor without duplicating code? - →What is the difference between a
constmethod and a[[nodiscard]]method?
MiddleTheoryCommonWhat does decltype(expr) deduce and how does it differ from auto?
What does decltype(expr) deduce and how does it differ from auto?
decltype(name) gives the declared type exactly (with refs/cv). decltype((name)) adds & since the parenthesised name is an lvalue expression. auto strips refs and top-level cv unless &/const is added. decltype(auto) (C++14) deduces by decltype rules.
Common mistakes
- ✗Forgetting that
decltype((x))givesT&, notT— common gotcha - ✗Using
autoto forward a return value and losing reference - ✗Mixing
decltype(auto)andauto&&without understanding the difference
Follow-up questions
- →What's the difference between
auto&&anddecltype(auto)in return types? - →When would
decltype(auto)give wrong results?
MiddleTheoryCommonWhy does an empty struct occupy 1 byte? What is the minimum addressable unit?
Why does an empty struct occupy 1 byte? What is the minimum addressable unit?
Every distinct object must have a unique address; sizeof == 0 would let two objects share one. So the standard requires sizeof(T) >= 1. Exception: Empty Base Optimisation — an empty class used as a base may take zero space inside the derived object.
Common mistakes
- ✗Confusing EBO with zero-size standalone objects — EBO only applies to base sub-objects, not standalone instances
- ✗Not knowing that
[[no_unique_address]](C++20) achieves EBO-like size savings for member variables too - ✗Checking
sizeofof a base class to predict how much it adds to a derived class — EBO may eliminate the cost entirely
Follow-up questions
- →How does
[[no_unique_address]]differ from EBO in practice? - →When would you intentionally use an empty struct as a tag type?
MiddleTheoryCommonHow does the compiler decide padding inside a struct, and how do you minimise it?
How does the compiler decide padding inside a struct, and how do you minimise it?
Each member sits at an offset that's a multiple of alignof(T); padding fills gaps and the struct aligns to the largest member. Reorder largest-first to shrink. [[no_unique_address]] (C++20) lets empty members share storage; #pragma pack overrides alignment.
Common mistakes
- ✗Using
#pragma pack(1)everywhere and tanking performance - ✗Hand-counting size without considering padding —
sizeofis the truth - ✗Forgetting that alignment can differ between compilers/architectures
Follow-up questions
- →How does
[[no_unique_address]]save space for empty types? - →Why does packed access cost extra on ARM compared to x86?
MiddleTheoryCommonAll uses of static in C++. What is the static initialisation order fiasco?
All uses of static in C++. What is the static initialisation order fiasco?
static has four uses: (1) static storage for locals (lazy init); (2) internal linkage for globals/functions; (3) class members shared across instances; (4) static-local in methods. SIOF: init order of non-local statics across TUs is undefined — A may use uninitialised B.
Common mistakes
- ✗Defining a non-trivial global object that depends on another TU's global — classic SIOF trigger
- ✗Thinking
staticclass members are automatically defined — they must be defined in exactly one.cpp(unlessinlinein C++17) - ✗Confusing the three different meanings of
staticwhen reading unfamiliar code
Follow-up questions
- →How does the Meyers singleton guarantee initialisation happens before first use?
- →Is the initialisation of a
constexprglobal subject to SIOF?
MiddleTheoryCommonWhat are structured bindings (C++17) and what can they decompose?
What are structured bindings (C++17) and what can they decompose?
auto [a, b, c] = expr; decomposes a tuple-like value (aggregate, std::pair, std::tuple, or class with get<I> + tuple_size) into named variables. auto& for refs, const auto& for const views. Each binding's type is deduced from its source member.
Common mistakes
- ✗Forgetting to use a reference and copying every element while iterating a map
- ✗Trying to bind a struct without all-public members — needs aggregate or tuple-like protocol
- ✗Using structured bindings to destructure into class members — not allowed
Follow-up questions
- →How do you make your class destructurable via the tuple protocol?
- →What's the difference between
auto [a, b]andauto& [a, b]?
MiddleTheoryCommonWhat is thread_local storage and what are its costs?
What is thread_local storage and what are its costs?
thread_local T x; gives each thread its own x, initialised on first use, destroyed at thread exit. Used for per-thread caches, RNGs, error state. Cost: extra indirection per access (TLS slot); shared libraries may require __tls_get_addr.
Common mistakes
- ✗Initialising thread_local with cross-thread data — race on first use
- ✗Forgetting destructor runs at thread exit; threads created via raw
pthread_createmay skip it - ✗Storing too much in thread_local — multiplies memory by thread count
Follow-up questions
- →What's the difference between
thread_localand__thread? - →How does TLS interact with
dlopenof shared libraries?
MiddleTheoryCommonWhat is undefined behaviour? Give examples.
What is undefined behaviour? Give examples.
UB means the standard places no constraints on what the program may do; the compiler may assume UB never occurs and optimise accordingly. Examples: signed overflow, null/dangling deref, out-of-bounds, uninitialised reads, data races. Not a runtime error — debug may pass, release may fail.
Common mistakes
- ✗Assuming UB 'works in practice' — optimisers exploit UB to remove branches, creating security vulnerabilities
- ✗Thinking signed integer overflow wraps around — it does for unsigned, but signed overflow is UB
- ✗Not using sanitisers (
-fsanitize=undefined,address) — UB is invisible without them
Follow-up questions
- →What is implementation-defined behaviour and how does it differ from UB?
- →How do compilers use UB to justify removing null checks or loop bounds?
MiddleTheoryCommonWhat is uniform initialisation? What is aggregate initialisation?
What is uniform initialisation? What is aggregate initialisation?
Uniform init (C++11) uses {} everywhere and bans narrowing (int x{3.14} is an error). Aggregate init applies when a class has no user ctors, no private/protected non-static members, no virtuals, no non-public bases — T obj{a, b, c} then fills members in order without a ctor.
Common mistakes
- ✗Unexpected
std::initializer_listconstructor call —vector<int> v{3}creates a 1-element vector, not a 3-element one - ✗Thinking aggregate init works for classes with any user-declared constructor — even a defaulted one disqualifies before C++20
- ✗Forgetting that
{}zero-initialises built-in types:int x{}is 0, unlikeint x;which is indeterminate
Follow-up questions
- →How does designated initialisation (C++20) improve aggregate initialisation?
- →What is the difference between
T{}andT()for a class type?
MiddleTheoryCommonWhat's the difference between default-, value-, and zero-initialisation?
What's the difference between default-, value-, and zero-initialisation?
Default-init T x; leaves built-ins indeterminate (UB to read) and calls the default ctor for classes. Value-init T x{}/T() zeros built-ins; for a class without a user-provided ctor it zeros members then runs the generated ctor. Zero-init is a separate stage for namespace-scope objects.
Common mistakes
- ✗Writing
int x;and reading it — UB; useint x{};for zero - ✗Forgetting that
T x();is a function declaration, not value-init (most vexing parse) - ✗Using
= {}and being surprised it copy-initialises (but with empty list)
Follow-up questions
- →What does
T()do for a class with a user-provided default ctor? - →Why is uniform initialisation
{}preferred for safety?
SeniorTheoryCommonHow do constexpr, consteval, and constinit differ?
How do constexpr, consteval, and constinit differ?
constexpr means a function may run during constant evaluation — or at runtime, depending on arguments. consteval (C++20) makes a function immediate: every call must evaluate at compile time. constinit (C++20) forces compile-time initialization of a static variable, killing the init order fiasco, but the variable stays mutable.
Common mistakes
- ✗Thinking
constexprguarantees compile-time evaluation — it only does so in a constant-evaluation context - ✗Expecting
constinitto make a variable usable in constant expressions — it does not implyconst - ✗Calling a
constevalfunction with a runtime argument and being confused by the hard compile error
Follow-up questions
- →When would you pick
constinitoverconstexprfor a global? - →Can a
constevalfunction call aconstexprfunction, and vice versa?
SeniorTheoryCommonWhat is a narrowing conversion and where does C++ forbid it?
What is a narrowing conversion and where does C++ forbid it?
A narrowing conversion is an implicit conversion that may lose information — floating-point to integer, a wider to a narrower type, or a value not representable in the target. C++ forbids narrowing inside brace initialization: int x{3.5}; is ill-formed while int x = 3.5; compiles.
Common mistakes
- ✗Believing
{}initialization is purely cosmetic — it actually adds a narrowing diagnostic plain=lacks - ✗Assuming
longtointis fine inside braces — it narrows unless the value is a fitting constant - ✗Forgetting
doubletointtruncation is narrowing, soint v{computeRatio()};fails to compile
Follow-up questions
- →Why is narrowing from a fitting constant expression explicitly allowed?
- →How does narrowing detection interact with
auto x{...}?
SeniorTheoryCommonWhat are std::variant and std::any? When to use each?
What are std::variant and std::any? When to use each?
std::variant<A,B,C> is a type-safe tagged union holding one value from a fixed compile-time set; access via std::get<T> or std::visit. std::any is a type-erased container with the set open at runtime. Use variant for known alternatives; any for dynamic types.
Common mistakes
- ✗Using
std::anywherestd::variantwould do — variant is safer, faster, and avoids heap allocation - ✗Forgetting that
std::get<T>(v)throwsstd::bad_variant_accessifvdoesn't holdT— usestd::get_iffor non-throwing access - ✗Assuming
std::anyis zero-cost — type erasure and small-buffer optimisation have overhead; benchmark if used in a hot path
Follow-up questions
- →How does
std::visitwith a lambda visitor replace a chain ofif/dynamic_castchecks? - →What is
std::monostateand why would you put it in avariant?
JuniorCodeOccasionalWhat do c, c + 1, and char(c + 1) print?
What do c, c + 1, and char(c + 1) print?
A 66 B. c prints as the character A. In c + 1 the char is promoted to int, so the result is 66, printed as a number. Casting back with char(c + 1) makes it a char again, so the stream prints 'B' (ASCII 66).
Common mistakes
- ✗Thinking char arithmetic stays char-typed
- ✗Believing streaming a char prints its numeric code
- ✗Missing that the explicit char cast changes how the stream formats it
Follow-up questions
- →Why does
std::cout << cprint a letter butstd::cout << c + 1print a number? - →What integer type does a
charpromote to in arithmetic?
JuniorCodeOccasionalWhat do 5 / 2, 5.0 / 2, and 7 % 3 print?
What do 5 / 2, 5.0 / 2, and 7 % 3 print?
2 2.5 1. 5 / 2 is integer division and truncates toward zero. 5.0 / 2 promotes the int to double, giving 2.5. % is the integer remainder. Note -7 % 3 is -1 in C++ — the result's sign follows the dividend.
Common mistakes
- ✗Expecting
5 / 2to give2.5because the math result is fractional - ✗Thinking
%rounds the quotient instead of returning the remainder - ✗Assuming
-7 % 3is2— in C++ the sign follows the dividend, so it is-1
Follow-up questions
- →What does
-7 % 3evaluate to in C++ and why? - →How do you force floating-point division between two
intoperands?
JuniorCodeOccasionalWhat does i = i++ + ++i; print?
What does i = i++ + ++i; print?
Undefined behavior: i is modified twice (i++ and ++i) and read with no sequence point separating the side effects, so the standard imposes no result — different compilers print different values and the program is invalid.
Common mistakes
- ✗Computing one 'correct' value as if the operations were ordered left to right
- ✗Calling it unspecified (one of several valid outputs) rather than undefined (an invalid program)
- ✗Assuming a newer standard defines the result
Follow-up questions
- →What is the difference between unspecified behavior and undefined behavior?
- →Which C++17 expressions did gain a guaranteed evaluation order?
MiddleTheoryOccasionalWhat are bit-fields? Use cases and layout rules.
What are bit-fields? Use cases and layout rules.
A bit-field is a member with colon and bit count: unsigned int flags : 3;. Compiler packs them into storage units. Ordering inside a unit is implementation-defined; a zero-width unnamed field forces a new unit; you cannot take a bit-field's address.
Common mistakes
- ✗Assuming bit-field layout is portable across compilers and endianness — it is not; use manual bit masking for cross-platform protocols
- ✗Using signed bit-fields without explicit
signed— the signedness of a plainintbit-field is implementation-defined - ✗Taking the address of a bit-field member — not allowed; use a temporary variable
Follow-up questions
- →What is the difference between
int x : 0;and omitting the field entirely? - →When would you prefer
std::bitsetor manual bit masking over bit-fields?
MiddleCodeOccasionalWhat does std::cout << i++ << i++ << i++ print?
What does std::cout << i++ << i++ << i++ print?
Through C++14 the operand evaluation order of << was unspecified, so it could print 2 1 0 or 0 1 2. C++17 made << operands evaluate left to right, so it now reliably prints 0 1 2. General function-argument order is still unspecified — avoid such side effects.
Common mistakes
- ✗Assuming chained
<<always evaluated left to right before C++17 - ✗Calling it undefined behavior — it is unspecified, since each
i++is sequenced across a separate<<call - ✗Generalizing the C++17 fix to all function arguments
Follow-up questions
- →What is the difference between unspecified evaluation order and undefined behavior here?
- →Does the C++17 left-to-right rule apply to ordinary function arguments?
MiddleTheoryOccasionalWhat is the most vexing parse and how do you avoid it?
What is the most vexing parse and how do you avoid it?
When a declaration could parse as a function or as object init, C++ picks the function. T x(MyArg()); looks like construction but parses as a function x taking a pointer-to-function-returning-MyArg. Fix with brace init: T x{MyArg{}}; or a named argument.
Common mistakes
- ✗Writing
Foo a();for a default-constructed Foo — declares a function - ✗Trying to debug 'Foo doesn't have method X' on something that's actually a function pointer
- ✗Not using uniform initialisation as a default to avoid the trap
Follow-up questions
- →Why does the language disambiguate toward functions?
- →How does C++11 brace init close this gap?
MiddleDebuggingOccasionalWhy can this sum-of-squares loop be undefined?
Why can this sum-of-squares loop be undefined?
int arithmetic overflows for large squares, and signed overflow is undefined behavior (not wraparound), so a[i] * a[i] and the accumulating int sum can both overflow silently. Fix: use a wider type — long long sum and static_cast<long long>(a[i]) * a[i].
Common mistakes
- ✗Believing signed overflow wraps around like unsigned instead of being UB
- ✗Assuming the multiplication promotes to double automatically
- ✗Blaming the loop index instead of the accumulator width
Follow-up questions
- →Why is signed integer overflow undefined behavior while unsigned wraps?
- →Why must you cast
a[i]tolong longbefore the multiplication, not after?
MiddleCodeOccasionalWhat does this signed/unsigned comparison print?
What does this signed/unsigned comparison print?
0 (false). In a mixed comparison the int is converted to unsigned, so -1 becomes a huge value (UINT_MAX), which is not less than 1. This usual-arithmetic-conversion trap also breaks for (size_t i = v.size() - 1; i >= 0; --i).
Common mistakes
- ✗Reasoning about the operands as mathematical integers, ignoring the conversion
- ✗Believing the unsigned operand is promoted to signed
- ✗Writing
size_tcountdown loops that never terminate
Follow-up questions
- →What are the usual arithmetic conversions for mixed signed/unsigned operands?
- →Why does
for (size_t i = n - 1; i >= 0; --i)loop forever?
MiddleDesignOccasionalA codebase passes raw int identifiers like a user id and an order id around, and they get mixed up accidentally at call sites. Explain what a strong typedef is, how it differs from a plain typedef/alias, and how you implement one in C++ since the language has no native syntax for it.
A codebase passes raw int identifiers like a user id and an order id around, and they get mixed up accidentally at call sites. Explain what a strong typedef is, how it differs from a plain typedef/alias, and how you implement one in C++ since the language has no native syntax for it.
typedef/alias gives a new name, not a new type: typedef int UserId; lets UserId and OrderId mix freely. A strong typedef wraps the type in a distinct class: struct UserId { int v; }; (or boost::strong_typedef). C++ has no native syntax — composition is the idiom.
Common mistakes
- ✗Believing
using UserId = int;is type-safe — it isn't - ✗Forgetting to delete unwanted conversions when wrapping
- ✗Adding too many implicit conversions and losing the safety property
Follow-up questions
- →How does
enum classprovide a kind of strong typing for integers? - →What's the proposed
class enum/ opaque-type syntax for future C++?
SeniorTheoryOccasionalWhat are the precise rules of aggregate initialization in modern C++?
What are the precise rules of aggregate initialization in modern C++?
An aggregate is an array or a class with no user-declared constructors, no private non-static data members, and no virtual functions. Brace-init fills members in declaration order; omitted trailing members are value-initialized. C++20 adds designated initializers (.x = 1).
Common mistakes
- ✗Adding a single defaulted constructor in-class and unknowingly breaking aggregate status (C++20)
- ✗Writing designated initializers out of declaration order — ill-formed in C++, unlike C
- ✗Forgetting that omitted members are value-initialized, not left indeterminate
Follow-up questions
- →Why did C++17 change whether bases participate in aggregate initialization?
- →How does
std::arrayrely on being an aggregate for brace-init?
SeniorDebuggingOccasionalWhen exactly does a reference dangle, and how does lifetime extension change that?
When exactly does a reference dangle, and how does lifetime extension change that?
A reference dangles when the object it binds to is destroyed while the reference still lives — using it is undefined behaviour. Binding a const T& to a temporary extends that temporary to the reference's scope. But extension applies only to the direct binding: returning the reference does not extend it.
Common mistakes
- ✗Returning a
const T&to a local or temporary — extension does not survive the return - ✗Storing a lifetime-extending reference in a struct member and expecting the temporary to persist
- ✗Binding
const auto& x = vec.front();then mutatingvecso reallocation invalidatesx
Follow-up questions
- →Why does binding through a function parameter not extend the temporary's lifetime?
- →How do dangling references differ from dangling pointers in detectability?
SeniorDebuggingOccasionalWhat is the strict aliasing rule and how can it bite you?
What is the strict aliasing rule and how can it bite you?
Strict aliasing says a stored value may be accessed only through an lvalue of its own type, a compatible type, or char. Reading a float through an int* is undefined behaviour, so the optimizer assumes such pointers never overlap and may reorder or drop loads. The safe tools are std::memcpy and std::bit_cast.
Common mistakes
- ✗
reinterpret_cast-ing between unrelated pointer types and dereferencing — textbook UB - ✗Assuming
-O0behaviour proves correctness — strict aliasing breaks only when the optimizer runs - ✗Believing
union-based punning is portable C++ — it is well-defined in C but UB in C++
Follow-up questions
- →Why is
std::bit_caststrictly better than amemcpy-based pun for trivial types? - →How does
char*get a special exemption from the aliasing rule?
JuniorTheoryRareWhat are ASCII and Unicode?
What are ASCII and Unicode?
ASCII is 7-bit, 128 characters. Unicode assigns code points to every character; UTF-8 is variable 1–4 bytes and ASCII-compatible, UTF-16 is 2 or 4 bytes.
Common mistakes
- ✗Treating
char*length as character count — in UTF-8,strlen()counts bytes, not Unicode code points or grapheme clusters - ✗Using
toupper()on UTF-8 bytes — it only works for ASCII; use ICU orstd::localefor Unicode case conversion - ✗Assuming one code point = one visible character — combining diacritics, emoji sequences, and grapheme clusters are multi-code-point glyphs
Follow-up questions
- →What is a BOM (Byte Order Mark) and when does it cause problems in UTF-8 files?
- →How does
std::wstring_convert(deprecated in C++17) differ fromstd::codecvt?
JuniorTheoryRareWhat bitwise operations exist in C++? What is Boolean algebra?
What bitwise operations exist in C++? What is Boolean algebra?
Six bitwise operators: & (AND), | (OR), ^ (XOR), ~ (NOT), <</>> (shifts). Boolean algebra: AND/OR/NOT identities (De Morgan: ~(A & B) == ~A | ~B). Idioms: set x |= 1 << n, clear x &= ~(1 << n), test (x >> n) & 1.
Common mistakes
- ✗Confusing
&&(logical AND) with&(bitwise AND) — subtle bugs when used with non-boolean values - ✗Left-shifting a signed integer so that the sign bit is affected — undefined behaviour in C++ (use
unsignedfor bit manipulation) - ✗Using right shift on a signed negative integer — implementation-defined (arithmetic vs logical shift)
Follow-up questions
- →How do you check if an integer is a power of two using bitwise operations?
- →What is two's complement and why does C++ rely on it (guaranteed since C++20)?
SeniorTheoryRareWhat is a trivial type, and why does triviality matter for memcpy?
What is a trivial type, and why does triviality matter for memcpy?
A trivial type has compiler-provided, non-virtual special members — no user logic runs on copy or destruction. Only trivially copyable types may be memcpy'd: the byte copy yields a valid object since there are no invariants to preserve. memcpy'ing a std::string is undefined behaviour.
Common mistakes
- ✗Confusing trivial with standard-layout — they are independent properties,
memcpyneeds trivially copyable - ✗
memcpy'ing a class with astd::stringmember, corrupting heap ownership and double-freeing - ✗Assuming a user-declared (even
= default) special member outside the class body keeps the type trivial
Follow-up questions
- →How does
std::is_trivially_copyablediffer fromstd::is_trivial? - →Why may
memcpybetween distinct trivially copyable types still be UB?