Junior C++ preparation

Junior C++ Backend Interview Questions

15 selected Junior C++ interview questions for backend developers who need to explain language fundamentals and basic ownership clearly.

Start a Junior C++ AI InterviewNo credit card required. 1 free session available.
Technical interview practice in EnglishA mode where non-native speakers can practice passing technical interviews.

Resource Management

1Explain RAII and how it shapes exception-safe resource management in C++ backend services.

RAII (Resource Acquisition Is Initialization) means a C++ object owns a resource and releases it from its destructor. Because local objects are destroyed automatically when their lifetime/scope ends, including during exception stack unwinding, RAII gives deterministic cleanup and makes error paths exception-safe. In backend services this applies not only to memory but also to file descriptors, sockets, mutex locks, database handles, transactions, and other OS or application resources.

Try answering this question with an AI coach

Memory Management

2Compare std::unique_ptr and std::shared_ptr and describe when each is appropriate in backend APIs.

std::unique_ptr represents exclusive ownership: it is cheap, movable but not copyable, and is appropriate for a single owner or for APIs that transfer ownership. std::shared_ptr represents shared ownership: it is copyable and keeps an object alive through reference counting until the last strong owner releases it. In backend APIs, use unique_ptr when ownership is transferred, shared_ptr only when multiple independent owners must extend lifetime, and references or raw pointers for non-owning access. std::make_shared is commonly preferred when creating shared_ptr objects because it is efficient and exception-safe.

Try answering this question with an AI coach

Type System

3What is move semantics, and how do you implement correct move construction and move assignment?

Move semantics lets C++ transfer resources from temporary or otherwise expendable objects instead of copying them. It uses rvalue references such as T&& and std::move, which is a cast that enables move overloads to be selected; std::move does not itself move anything. A correct move constructor initializes a new object by taking the source object's resource and leaving the source valid, destructible, and assignable. A correct move assignment operator transfers into an existing object, handles or tolerates self-assignment, releases or reuses the destination's current resource, takes the source resource, and leaves the source safe. Move operations should often be noexcept so standard containers can use them during reallocation while preserving exception guarantees.

Try answering this question with an AI coach

4Describe const-correctness in C++ APIs and how to design const member functions effectively.

Const-correctness means expressing through the type system which operations do not modify an object's observable or logical state. A const member function has a const-qualified this object, so it cannot modify non-mutable data members or call non-const member functions on the same object. Good API design marks read-only queries const, returns values or const references/pointers when appropriate, and avoids exposing mutable internal state from const functions. mutable should be reserved for implementation details that do not change logical state, such as caches, lazy values, metrics, or mutexes. const is an API contract about mutation, not an automatic guarantee of thread safety; concurrency guarantees need separate implementation and documentation.

Try answering this question with an AI coach

5What is std::byte and how should raw binary buffers be represented safely in C++?

std::byte is a distinct type for representing raw binary data as bytes, not characters or arithmetic integers. It improves type safety because byte buffers are not accidentally treated as text or numeric values, while still supporting bitwise operations. Raw binary buffers should usually be represented with byte-oriented storage such as std::vector<std::byte> or std::array<std::byte, N>, and passed through non-owning APIs as std::span<std::byte> or std::span<const std::byte>. Serialization code should explicitly encode and decode values rather than relying on arbitrary object layouts.

Try answering this question with an AI coach

Object Lifetime

6Describe the Rule of Zero, Rule of Three, and Rule of Five and when each applies.

Rule of Zero: prefer classes that do not declare custom destructor/copy/move operations; let RAII members such as std::string, std::vector, std::unique_ptr, file/socket wrappers, etc. manage resources. Rule of Three: if a class manually manages a resource and needs a custom destructor, copy constructor, or copy assignment operator, it usually needs all three to define correct copy/ownership behavior. Rule of Five: in C++11 and later, such types should also consider move constructor and move assignment operator. Use Rule of Zero for most application types; use Rule of Three/Five when the type directly owns a resource or has nontrivial ownership/lifetime semantics.

Try answering this question with an AI coach

Error Handling

7Explain C++ exception handling, stack unwinding, destructor interaction, and service exception boundaries.

C++ exceptions transfer control from a `throw` expression to the nearest matching `catch`. During propagation, stack unwinding destroys fully constructed automatic objects in reverse order, so RAII cleanup runs automatically. Destructors should generally not throw; if an exception escapes a `noexcept` destructor or another exception escapes during active unwinding, the program calls `std::terminate`. Exceptions should usually be caught by reference, commonly `const&`, to avoid slicing and unnecessary copies. Backend services should define exception boundaries, such as request handlers, worker-thread entry points, RPC/HTTP framework callbacks, and `main`, where exceptions are logged, converted to error responses or status codes, and prevented from escaping into inappropriate contexts such as C APIs, destructors, threads, or `noexcept` functions.

Try answering this question with an AI coach

8What happens if a destructor throws, and how should backend types report cleanup failures?

Destructors are implicitly `noexcept(true)` in normal cases, so if an exception escapes such a destructor, `std::terminate` is called. Even if a destructor is explicitly declared `noexcept(false)`, throwing during stack unwinding is dangerous because a second escaping exception while another exception is active also terminates the program. Therefore destructors should perform best-effort cleanup and should not let exceptions escape. Backend types should report cleanup failures through explicit operations such as `close()`, `flush()`, `commit()`, `stop()`, or `shutdown()` that return an error/`expected` or throw before destruction. The destructor can log, emit metrics, suppress errors, or perform safe fallback cleanup, but should not be the primary error-reporting channel for actionable failures.

Try answering this question with an AI coach

Standard Library

9How does std::vector manage capacity, growth, reallocation, and iterator stability?

std::vector stores elements contiguously and tracks both size and capacity. size is the number of constructed elements; capacity is the amount of allocated element storage available before another allocation is needed. When adding elements would exceed capacity, vector allocates a larger block, typically using an implementation-defined geometric growth strategy, moves or copies existing elements, destroys the old ones, and releases the old storage. reserve(n) increases capacity without changing size, while resize(n) changes size by constructing or destroying elements. Reallocation invalidates all iterators, references, and pointers to elements; even without reallocation, operations such as insert and erase can invalidate positions at or after the modification point.

Try answering this question with an AI coach

10What invalidation rules should you know for contiguous and node-based standard containers?

Invalidation rules depend on the container and the operation. Contiguous containers such as vector and string have fragile iterator/reference stability: growth may reallocate and invalidate all iterators, references, and pointers, and insert/erase can shift elements and invalidate positions at or after the change even without reallocation. Node-based ordered containers such as list, map, set, and their multi variants generally keep iterators and references to existing non-erased elements stable across insert; erasing an element invalidates the iterator/reference to that erased element. Unordered containers also store elements in nodes, so references and pointers to elements are generally stable across rehash, but rehash invalidates iterators. deque has special segmented-storage rules. In practice, check the specific container and operation before storing iterators or references across modifications.

Try answering this question with an AI coach

11Compare std::map, std::unordered_map, and flat-map-style containers for backend lookup tables.

std::map is an ordered, usually tree-based associative container with logarithmic lookup/insert/erase; it is useful when sorted iteration, range queries, or ordering guarantees matter. std::unordered_map is hash-table based with average constant-time exact-key operations and no key ordering; it is often a good default for large mutable lookup tables when hashing is good. A flat-map-style container stores sorted key/value pairs contiguously, giving good cache locality and fast iteration/binary-search lookup, but insertion and erasure in the middle are linear. For backend lookup tables, choose based on whether the workload needs ordering/ranges, mostly exact lookups, frequent mutation, predictable latency, memory overhead, and cache behavior.

Try answering this question with an AI coach

12Explain std::optional and typical backend use cases for representing absent values.

std::optional<T> represents either a contained T value or no value. The empty state is represented by std::nullopt; code can check has_value() or use the optional in a boolean context, access the value with * or value(), and supply a default with value_or(). In backend code it is useful for nullable database fields, optional request/configuration fields, cache or repository misses where absence is expected, and domain states where a sentinel like -1 or an empty string would be ambiguous. It models absence of a value, not polymorphism or rich error information.

Try answering this question with an AI coach

13What are std::string_view and std::span, and what lifetime hazards do non-owning views introduce?

std::string_view is a non-owning view of a contiguous character sequence; std::span<T> is a non-owning view of a contiguous sequence of T. They are useful for zero-copy parameters and buffer APIs because they carry a pointer and a length without allocating or owning memory. The main hazard is lifetime: the referenced storage must outlive the view and must not be invalidated while the view is used. Returning or storing a view to a temporary, local object, destroyed object, or reallocated container can leave a dangling view.

Try answering this question with an AI coach

Concurrency

14How do std::mutex, std::shared_mutex, and std::recursive_mutex differ, and when would you choose each?

std::mutex provides exclusive locking: only one thread can hold it, so it is the default choice for protecting shared mutable state. std::shared_mutex supports shared reader locks and exclusive writer locks: many readers may hold the lock concurrently, but writers need exclusive access. Choose it for read-mostly data when reader concurrency is worth the overhead and when writer fairness/starvation is acceptable or handled. std::recursive_mutex is an exclusive mutex that the same thread may lock multiple times and must unlock the same number of times; use it rarely, mainly for legacy or re-entrant code, because it can hide poor locking design.

Try answering this question with an AI coach

Language Features

15Explain lambda capture modes and how captures interact with object lifetimes in callbacks.

A lambda can capture variables by value (`[x]` or `[=]`), by reference (`[&x]` or `[&]`), capture `this`, or use init-capture such as `[p = std::move(ptr)]`. Value captures copy the captured object into the closure when the lambda is created; reference captures store references, so the original objects must outlive all lambda invocations. In callbacks, stored lambdas, or asynchronous work, reference captures and `this` captures are dangerous because locals or the object may be destroyed before invocation. Prefer value captures for needed data, move/init-capture for ownership, or deliberate `shared_ptr`/`weak_ptr` patterns when object lifetime must be extended or checked.

Try answering this question with an AI coach