← Chicago Interview Insights

Chicago·Software Engineer·Technical Phone Screen·Junior

JuniorPrefer not to say
May 2026Chicago

Summary

C++ new-grad interview that was basically a two-part coding gauntlet: implement UniquePtr and SimpleVector from scratch, then field some pretty pointed follow-ups on move semantics and exception safety. The whole thing felt like they wanted to see if you actually understood RAII and ownership, not just whether you'd memorized the STL API.

Questions Asked (6)

Q1

Implement a move-only smart pointer class template that owns a single heap-allocated object, supports get/release/reset/dereference/arrow operators, and correctly handles self-move-assignment without leaking or double-freeing.

Technical Trade-offsAlgorithms & Data StructuresSystem Design
Author's notes

I got the basic structure down pretty fast but fumbled on the sequencing inside move assignment.

Create a free account to read the full note

AI HintsAI Generated

Suggested Approach

Start by clarifying the requirements and constraints (e.g., C++ version, exception safety). Then outline the class design, emphasizing move semantics and self-move-assignment handling. Finally, discuss implementation details, including the rule of five and potential pitfalls.

Pro tip: Mention that self-move-assignment can be handled by checking for self-assignment or by using the copy-and-swap idiom, but note that the latter requires a swap function. Also, consider using std::exchange for concise move operations.

1. Clarify Requirements

Ask about the expected C++ standard, exception safety guarantees, and whether the pointer should support custom deleters or arrays.

2. Design Class Interface

Define the class template with a raw pointer member. Declare constructors, destructor, move constructor, move assignment, and deleted copy operations. Include get, release, reset, operator*, and operator->.

3. Implement Move Semantics

Implement move constructor and move assignment to transfer ownership. For move assignment, handle self-assignment by checking if this != &other, or use a swap-based approach. Ensure the old resource is released.

4. Implement Other Members

Implement destructor to delete the pointer. Implement get, release, reset, and dereference operators. Ensure reset deletes the current pointer and takes ownership of the new one.

5. Test and Validate

Mention testing scenarios: self-move-assignment, move from, reset, release, and exception safety. Consider using static_assert to enforce move-only semantics.

Key Points to Mention

  • Rule of Five: need to define destructor, move constructor, move assignment, and delete copy constructor and copy assignment.
  • Self-move-assignment: check for self-assignment or use swap idiom to avoid deleting the resource before transferring.
  • Use of std::exchange in move operations for concise and correct code.
  • Exception safety: ensure no leaks if an exception occurs during reset or move assignment.
  • Ownership semantics: release() relinquishes ownership without deleting, reset() deletes and takes new ownership.
  • Comparison with std::unique_ptr: discuss differences and why one might implement a custom smart pointer.

AI-generated suggestions, not part of the candidate's original notes. May be inaccurate — verify before relying on them.

Q2

Implement a dynamic array class template with separate size and capacity tracking, using raw untyped storage so you never default-construct unused slots. Must support push_back, pop_back, reserve, clear, copy, and move operations.

System DesignTechnical Trade-offsAlgorithms & Data Structures
Author's notes

This one took longer than I expected.

Create a free account to read the full note

AI HintsAI Generated

Suggested Approach

Start by clarifying requirements and constraints, then outline the design using raw storage and placement new. Walk through each operation, emphasizing exception safety, move semantics, and the separation of size and capacity. Conclude with trade-offs and potential optimizations.

Pro tip: Mention that you would use std::aligned_storage or a char buffer with proper alignment, and that you'd implement strong exception guarantees for push_back and reserve. This shows you understand low-level memory management and production-quality code.

1. Clarify requirements and constraints

Ask about alignment requirements, exception safety guarantees, and whether the container should support non-default-constructible types. Confirm that raw storage means no default construction of unused slots.

2. Design the class layout

Define member variables: pointer to raw storage, size, capacity. Explain how to allocate aligned memory (e.g., using operator new or std::aligned_alloc) and deallocate it.

3. Implement core operations

Describe push_back using placement new and handling reallocation; pop_back calling destructor; reserve allocating new storage and moving elements; clear destroying elements and setting size to 0.

4. Implement copy and move semantics

For copy constructor/assignment, allocate new storage and copy-construct elements. For move, steal the pointer and reset source. Discuss noexcept and self-assignment.

5. Discuss exception safety and trade-offs

Explain how to provide strong exception guarantee for push_back (e.g., copy-and-swap) and basic guarantee for others. Mention performance considerations and alternatives like std::vector.

Key Points to Mention

  • Use of placement new and explicit destructor calls to manage object lifetime in raw storage.
  • Proper memory alignment and deallocation to avoid undefined behavior.
  • Exception safety guarantees: strong for push_back, basic for others, and how to achieve them.
  • Move semantics: noexcept move constructor/assignment and efficient resource transfer.
  • Separation of size and capacity to avoid default-constructing unused slots.
  • Comparison with std::vector and when a custom dynamic array is justified.

AI-generated suggestions, not part of the candidate's original notes. May be inaccurate — verify before relying on them.

Q3

Explain the difference between copy semantics and move semantics, and point to specific places in your two implementations where each applies. Why is the smart pointer move-only while the vector supports both?

Technical Trade-offs
Author's notes

Felt pretty solid here.

Create a free account to read the full note

AI HintsAI Generated

Suggested Approach

Start by defining copy and move semantics in terms of resource ownership and performance, then walk through your two implementations to pinpoint where each applies. Explain the smart pointer's move-only design by contrasting exclusive ownership with the vector's need for both copy and move to support flexible usage.

Pro tip: Emphasize that move semantics are an optimization, not a replacement for copy semantics—this shows you understand when each is appropriate and avoids overusing moves. Also, mention that move-only types like smart pointers enforce correctness by preventing accidental copies of unique resources.

1. Define copy and move semantics

Explain that copy semantics duplicate an object's resources, while move semantics transfer ownership, leaving the source in a valid but unspecified state. Highlight the performance and ownership implications.

2. Map to your implementations

Identify specific classes/functions in your two implementations where copy and move occur. For example, in a vector-like class, copy happens in the copy constructor and move in the move constructor; in a smart pointer, move occurs in the move constructor and move assignment.

3. Explain smart pointer move-only rationale

Discuss that a smart pointer typically models exclusive ownership (like unique_ptr), so copying would lead to double deletion or ambiguity. Moving transfers ownership safely.

4. Explain vector's support for both

Describe that a vector manages a dynamic array and may need to copy elements for operations like copying the vector itself, but also benefits from moving elements during reallocation or when the vector is moved. This flexibility supports both value semantics and efficient resource transfer.

5. Conclude with trade-offs

Summarize that move-only types enforce unique ownership and prevent costly or incorrect copies, while types supporting both offer versatility at the cost of potential accidental copies. Relate this to design decisions in your implementations.

Key Points to Mention

  • Copy semantics duplicate resources (deep copy), move semantics transfer resources (shallow copy with nullification).
  • In the smart pointer implementation, the move constructor and move assignment transfer ownership; copy operations are deleted.
  • In the vector implementation, the copy constructor/assignment perform deep copies, while the move constructor/assignment steal the buffer.
  • Smart pointer is move-only to enforce unique ownership and prevent double-free errors.
  • Vector supports both because it often needs to be copyable (e.g., to store in containers) and movable for efficiency.
  • Move operations should be noexcept to enable optimizations like vector reallocation using moves.

AI-generated suggestions, not part of the candidate's original notes. May be inaccurate — verify before relying on them.

Q4

Why does the standard vector require a noexcept move constructor on T to actually use moves during reallocation? What breaks with the strong exception guarantee if moves can throw?

Technical Trade-offsSystem Design
Author's notes

This tripped me up more than I expected.

Create a free account to read the full note

AI HintsAI Generated

Suggested Approach

Explain that std::vector must provide the strong exception guarantee during reallocation, so it can only move elements if the move constructor is noexcept; otherwise, it copies to allow rollback. Then describe the specific failure scenario if moves could throw.

Pro tip: Mention that this is why it's crucial to mark move constructors noexcept when possible, and that types with throwing moves can silently degrade performance in vectors.

1. State the guarantee

Clarify that std::vector reallocation must offer the strong exception guarantee: if an exception is thrown, the vector remains unchanged.

2. Explain the reallocation process

Describe how vector allocates new storage, then transfers elements from old to new storage, and finally deallocates old storage.

3. Contrast move vs copy

Explain that moving modifies the source, so if a move throws mid-transfer, the original elements may be left in a valid but unspecified state, making rollback impossible.

4. Describe the fallback

Explain that if the move constructor is not noexcept, vector uses copy construction instead, because copies leave the source intact, allowing rollback.

5. Conclude with implications

Summarize that this design ensures exception safety at the cost of performance, and encourage marking move constructors noexcept when safe.

Key Points to Mention

  • Strong exception guarantee: no effects if an exception is thrown.
  • Reallocation involves allocating new memory and transferring elements.
  • Move constructor modifies the source, potentially leaving it in an invalid state if an exception occurs.
  • Copy constructor leaves the source unchanged, enabling rollback.
  • std::vector uses std::move_if_noexcept to decide between move and copy.
  • Marking move constructors noexcept enables efficient moves in vector reallocation.

AI-generated suggestions, not part of the candidate's original notes. May be inaccurate — verify before relying on them.

Q5

How would you add custom deleter support to your smart pointer without paying any runtime overhead for the common case where you just use the default delete?

Technical Trade-offsSystem Design
Author's notes

Blanked for a second.

Create a free account to read the full note

AI HintsAI Generated

Suggested Approach

Start by clarifying the requirements: custom deleter support with zero overhead for the default case. Then propose a design using a type-erased deleter stored only when needed, such as a union or a template specialization that avoids storing a deleter for the default case. Explain how this achieves zero overhead by not increasing the size of the smart pointer and not adding runtime branches for the common case.

Pro tip: Mention that this is exactly how std::unique_ptr is implemented in major standard libraries: it uses the empty base optimization (EBO) or a compressed pair to store the deleter only when it's not empty, and for the default deleter (std::default_delete), it's an empty class, so no extra space is used. This shows you understand real-world implementations.

1. Clarify requirements and constraints

Confirm that 'no runtime overhead' means no extra memory footprint and no extra branches for the default case. Also confirm that custom deleters should be supported without affecting the default case's performance.

2. Explore design options

Discuss possible approaches: template parameter for deleter (like unique_ptr), type erasure (like shared_ptr), or a hybrid. Explain trade-offs: templates give zero overhead but increase code size; type erasure adds overhead but allows runtime flexibility.

3. Propose a zero-overhead design

Suggest using a template parameter for the deleter, defaulting to a stateless deleter. Use empty base optimization or compressed pair to avoid storing the deleter when it's empty. For stateful deleters, store them as members, but only when needed.

4. Explain how the default case avoids overhead

Detail that the default deleter is an empty class, so with EBO, the smart pointer's size is just the pointer. Also, the delete call is statically dispatched, so no virtual function or branch is needed.

5. Address potential pitfalls and alternatives

Mention that this design requires the deleter type to be part of the smart pointer's type, which may not be suitable for all use cases. If runtime polymorphism is needed, consider a type-erased wrapper but acknowledge the overhead.

Key Points to Mention

  • Template parameter for deleter (e.g., std::unique_ptr<T, Deleter>)
  • Empty Base Optimization (EBO) or compressed pair to eliminate storage for stateless deleters
  • Static dispatch of deleter call (no virtual function overhead)
  • Comparison with std::shared_ptr's type-erased deleter and its overhead
  • Impact on code size and compile times due to templates
  • Use of std::default_delete as the default deleter

AI-generated suggestions, not part of the candidate's original notes. May be inaccurate — verify before relying on them.

Q6

Which operations on your vector implementation invalidate iterators and references, and how does that compare to the behavior of std::vector?

Technical Trade-offsAPI & Integrations
Author's notes

Pretty quick answer: any reallocation invalidates everything, so push_back when size equals capacity is the main culprit.

Create a free account to read the full note

AI HintsAI Generated

Suggested Approach

Start by clearly stating the iterator/reference invalidation rules for your vector implementation, then systematically compare them to std::vector's rules. Highlight any deliberate design choices and trade-offs, and conclude with the practical implications for users.

Pro tip: Emphasize that iterator invalidation is a critical API contract; documenting and testing it thoroughly shows you understand the importance of predictable behavior in container design.

1. State your vector's invalidation rules

List the operations that invalidate iterators and references in your implementation, such as reallocation on push_back, insert, erase, reserve, resize, and shrink_to_fit.

2. Compare with std::vector's rules

Explain how std::vector handles the same operations, noting similarities and differences, especially regarding reallocation and element shifting.

3. Explain design rationale

Discuss why your implementation may differ, such as different growth strategies, memory management, or API guarantees, and the trade-offs involved.

4. Highlight practical implications

Describe how these invalidation rules affect usage, such as the need to refresh iterators after modifications, and any safety mechanisms you provide.

5. Conclude with testing and documentation

Mention how you ensure correctness through tests and documentation, and invite further questions about specific scenarios.

Key Points to Mention

  • Reallocation invalidates all iterators and references in both implementations.
  • Insert/erase in the middle invalidates iterators/references at or after the modification point.
  • std::vector guarantees no invalidation if capacity is sufficient for push_back/insert at end.
  • Your implementation's growth factor and its impact on invalidation frequency.
  • Differences in exception safety guarantees and their effect on invalidation.
  • The importance of documenting invalidation rules for users.

AI-generated suggestions, not part of the candidate's original notes. May be inaccurate — verify before relying on them.