C++ interview preparation

C++ Backend Developer Interview Questions

Selected C++ interview questions for backend developers, grouped by topic and rendered from the same question catalog that powers EngineerSpeak practice.

Start a C++ Backend 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

3How do custom deleters work in smart pointers, and when are they useful for C/C++ interoperability?

A custom deleter is callable cleanup logic used by a smart pointer instead of the default delete operation when the pointer releases its owned resource. It is useful for C/C++ interoperability when a resource must be released with a specific function such as fclose, close, curl_easy_cleanup, SSL_free, free, or a library destroy function. For unique_ptr, the deleter type is part of the unique_ptr type and can affect its size; stateless deleters may be optimized away, while function-pointer or stateful deleters add storage. For shared_ptr, the deleter is stored in the control block and runs when the last strong owner releases the object. Custom deleters let C resources participate in RAII safely.

Try answering this question with an AI coach

4Explain weak_ptr and shared_ptr control-block mechanics, including cycles, enable_shared_from_this, and refcount costs.

A shared_ptr manages shared ownership through a control block containing strong and weak reference counts plus cleanup information such as the deleter and allocator. Copying or destroying shared_ptr objects increments or decrements the strong count, typically with atomic operations, so separate shared_ptr objects can be manipulated safely across threads but each refcount update has cost. When the strong count reaches zero, the managed object is destroyed; the control block remains until weak references are also gone. weak_ptr points to the same control block without extending object lifetime; lock() returns a shared_ptr if the object is still alive and an empty shared_ptr otherwise. Cycles made only of shared_ptr leak because strong counts never reach zero, so weak_ptr is used for back-pointers or observer links. enable_shared_from_this lets an object already owned by shared_ptr create a new shared_ptr to itself using the existing control block, avoiding dangerous separate control blocks. Refcount costs include atomic increments/decrements, cache contention, control-block allocation, and overhead in hot paths.

Try answering this question with an AI coach

Type System

5What 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

6Describe 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

7What are move-only types and how do they influence API boundaries for sockets, file handles, and locks?

Move-only types are types that cannot be copied but can be moved. They are common for unique ownership of resources such as sockets, file descriptors, file handles, locks, and `std::unique_ptr`. Copy operations are deleted to avoid duplicated ownership and double release; move operations transfer ownership and leave the source valid but usually empty/non-owning. APIs should make ownership boundaries explicit: factories can return move-only objects by value, functions that take ownership can accept by value or rvalue reference, and functions that only inspect should take references, pointers, or other borrowing handles. Containers can store move-only values when elements are inserted or relocated by move.

Try answering this question with an AI coach

8Compare auto, decltype, decltype(auto), and template argument deduction in common backend code.

auto uses template-like deduction for variables: plain auto usually drops references and top-level const unless the declaration asks for them, such as auto&, const auto&, or auto&&. decltype(expr) inspects the declared type or expression type more exactly: an unparenthesized id-expression gives the declared type, while other lvalue expressions produce T&, xvalues produce T&&, and prvalues produce T. decltype(auto) deduces using decltype rules, often for return types when references must be preserved. Template argument deduction is similar to auto but depends on the parameter form, such as T, T&, const T&, or T&&, and has its own rules. Braced initializers are a common difference: auto x = {1,2} deduces std::initializer_list<int>, while a plain template parameter generally cannot deduce T from a bare braced initializer unless the parameter expects an initializer_list or another suitable type.

Try answering this question with an AI coach

Object Lifetime

9Describe 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

Language Semantics

10Describe C++ initialization forms and common pitfalls, including initializer_list and aggregates.

C++ has several initialization forms. Default initialization, such as `T x;`, calls a default constructor for class types but leaves automatic fundamental variables uninitialized. Value initialization, such as `T x{};` or `T()`, zero-initializes where applicable before constructor/member initialization. List initialization uses braces, rejects narrowing conversions, and has special overload resolution rules, including strong preference for viable `std::initializer_list` constructors. Aggregate initialization initializes aggregate members directly with braces; C++20 also supports designated initializers for aggregates, in declaration order. Common pitfalls include uninitialized local scalars, surprising `initializer_list` overload selection, narrowing errors with braces, the most-vexing parse with parentheses, and behavior changes when a type stops being an aggregate.

Try answering this question with an AI coach

11What is undefined behavior in C++, and how can it appear in backend production incidents?

Undefined behavior is behavior for which the C++ standard imposes no requirements after an invalid operation occurs. The program may appear to work, crash, corrupt data, expose security bugs, or be optimized into surprising behavior. Compilers assume UB does not happen and optimize based on that assumption, so issues may appear only in release builds or under production traffic. Backend incidents can come from dangling pointers/references, use-after-free, object lifetime violations, out-of-bounds access, signed integer overflow, data races, invalid casts, uninitialized reads, double frees, or strict-aliasing violations. Mitigation includes RAII and clear ownership/lifetime design, safer abstractions and bounds checks, testing/fuzzing, code review, static analysis, and sanitizers such as ASan, UBSan, and TSan.

Try answering this question with an AI coach

Object Model

12Explain virtual functions, vtables, dynamic dispatch cost, object slicing, and virtual destructor hazards.

A virtual function enables runtime polymorphism: when called through a base pointer or reference, the implementation for the object's dynamic type is selected. Most implementations store a hidden vptr in each polymorphic object pointing to a vtable of virtual function addresses for that dynamic type. The typical cost is an extra pointer in the object, an indirect call, possible cache/branch-prediction impact, and reduced inlining opportunities, although compilers can sometimes devirtualize. Object slicing occurs when a derived object is copied or stored by value as a base object, losing the derived part and dynamic behavior. If a base class is meant to be deleted through a base pointer, its destructor must be virtual; otherwise deleting a derived object through that base pointer has undefined behavior.

Try answering this question with an AI coach

Standard Library

13Describe std::chrono clocks, time points, and durations for service timeouts, metrics, and timestamps.

`std::chrono` models time with clocks, `time_point`s, and `duration`s. A `duration` is an interval with a unit, such as milliseconds or seconds. A `time_point` is a point on a specific clock's timeline. `steady_clock` is monotonic and should be used for elapsed time, service timeouts, deadlines, and latency measurements because it is not affected by wall-clock changes. `system_clock` represents civil/wall-clock time and is appropriate for timestamps, logging, persistence, and calendar conversion, but it can jump when the system time is adjusted. Timeouts should usually be based on `steady_clock::now() + duration`; machine timestamps should use a clearly documented wall-time format, typically UTC at API/storage boundaries.

Try answering this question with an AI coach

14Describe std::format and modern formatting facilities compared to iostreams and printf-style APIs.

`std::format` is the C++20 type-safe formatting facility inspired by fmtlib. It uses `{}` replacement fields and format specifications to produce formatted text without iostreams' stateful insertion syntax or `printf`-style C varargs. Compared with `printf`, it avoids many format/type mismatch problems; compared with iostreams, it is often clearer and easier to compose. fmtlib is the widely used library that preceded and influenced `std::format` and may provide broader support or newer features. User-defined types can be formatted with custom formatter support. In high-volume logging, performance depends on avoiding unnecessary formatting, conversions, and allocations, especially for disabled log levels; APIs that defer formatting or check log level first are preferable.

Try answering this question with an AI coach

Templates

15Explain C++ value categories and perfect forwarding, and why they matter for efficient generic APIs.

C++ value categories describe expressions: lvalues have identity and can be referred to after the expression; prvalues are pure rvalues such as many temporaries/computed values; xvalues are expiring objects whose resources may be reused. Perfect forwarding is the template technique of taking a forwarding reference, typically T&& where T is deduced, and forwarding with std::forward<T>(arg) so the caller's value category is preserved: lvalues remain lvalues and rvalues remain rvalues. Reference collapsing rules make this possible. It matters for generic backend APIs because wrappers, factories, dispatchers, and emplace-style functions can avoid unnecessary copies and preserve overload selection and move behavior.

Try answering this question with an AI coach

Concurrency

16Describe deadlock prevention and multi-mutex locking strategies using std::scoped_lock and std::lock.

Deadlock is prevented by avoiding circular waits: acquire locks in a consistent global order when possible, or acquire multiple mutexes with `std::lock`/`std::scoped_lock`, which use a deadlock-avoidance algorithm. `std::scoped_lock lock(a, b, ...)` is the simplest RAII form for locking several mutexes and automatically unlocking them on scope exit. With `std::lock`, first lock the mutexes, then attach RAII wrappers using `std::adopt_lock`, or use `std::unique_lock` with `std::defer_lock`. Keep critical sections short and avoid blocking operations or unknown callbacks while holding locks; deadlock avoidance does not automatically guarantee fairness or prevent all livelock/starvation scenarios.

Try answering this question with an AI coach

17Explain condition_variable usage patterns for waiting on state changes.

Use `std::condition_variable` to wait for a mutex-protected state predicate to change. The shared state is modified while holding the same mutex, then `notify_one` or `notify_all` wakes waiters. Waiters should use `cv.wait(lock, predicate)` or an equivalent loop because wakeups can be spurious and notifications are not remembered independently of the predicate state. `notify_one` wakes one waiter; `notify_all` wakes all waiters and is appropriate for broadcast state changes such as shutdown.

Try answering this question with an AI coach

18Design a thread-safe bounded queue using standard C++ primitives and define its API guarantees.

A bounded thread-safe queue can be built with a `std::mutex`, condition variables, a fixed-capacity container, and a shutdown/closed flag. `push` blocks, times out, or fails when the queue is full, which provides backpressure; `pop` blocks when empty. Both operations should wait on predicates such as `size < capacity || closed` and `!empty || closed`. On shutdown/close, wake blocked producers and consumers; reject new pushes and define whether consumers drain existing items or immediately stop. The API should specify blocking behavior, close semantics, return values, and concurrency guarantees.

Try answering this question with an AI coach

Database

19How do you execute a schema migration for a C++ service without downtime?

Use a staged expand-contract migration. First make backward-compatible schema additions, such as nullable columns or new tables, that do not break the currently running C++ service. Deploy compatible code that can handle both old and new representations, backfill existing data in small throttled batches, validate consistency, switch reads to the new schema, and only later remove old columns or code after all deployed versions no longer depend on them. For large tables, avoid long blocking DDL, use online/concurrent operations where supported, monitor locks/replication/resources, and keep rollback paths that work with partially deployed code and partially migrated data.

Try answering this question with an AI coach

20How should a C++ service read from database replicas while managing stale reads and read-your-writes guarantees?

Treat replica reads as an explicit consistency trade-off. Reads that can tolerate staleness may go to healthy replicas, but read-your-writes, transactional, or freshness-critical reads should go to the primary unless the chosen replica is known to have replayed the relevant write position, such as an LSN, timestamp, or version. Track replica lag and health, encode the required consistency per endpoint or request, and fall back to primary, wait for catch-up, or fail when replicas exceed the allowed staleness. Document the consistency model so callers know which reads may be stale.

Try answering this question with an AI coach