PracHub
QuestionsLearningGuidesInterview Prep
|Home/Software Engineering Fundamentals/Jump Trading

Implement a Simplified std::vector in C++: Growth, Memory, Exception Safety, and Type Traits

Last updated: Jul 2, 2026

Quick Overview

This question evaluates mastery of C++ low-level container implementation, including raw memory allocation versus object construction, object lifetime and the rule of five, move and copy semantics, exception-safety guarantees, type-traits-driven optimizations, and amortized-growth analysis for push_back.

  • hard
  • Jump Trading
  • Software Engineering Fundamentals
  • Software Engineer

Implement a Simplified std::vector in C++: Growth, Memory, Exception Safety, and Type Traits

Company: Jump Trading

Role: Software Engineer

Category: Software Engineering Fundamentals

Difficulty: hard

Interview Round: Take-home Project

Implement a simplified `std::vector<T>` from scratch in C++ — a dynamically growing array template. This is a classic C++ interview question at quantitative trading firms (asked frequently in C++ developer loops at top buy-side shops), and the interviewer is testing far more than the well-known "double the capacity" trick. A strong candidate is expected to demonstrate command of raw memory allocation vs. object construction, exception safety guarantees, and the use of type traits to optimize the implementation. Your `Vector<T>` should support at minimum: - Default construction, destruction - Copy construction/assignment and move construction/assignment (the rule of five) - `push_back` (and ideally `emplace_back`) with amortized $O(1)$ growth - `operator[]`, `size()`, `capacity()`, and `reserve()` You do not need iterators, `insert`/`erase`, or a custom allocator template parameter — but you must handle element type `T` correctly: `T` may be expensive to copy, may be move-only, may throw from its copy constructor, and may have a non-trivial destructor. ### Constraints & Assumptions - Target C++17 or later; you may use `<new>`, `<memory>`, `<type_traits>`, and `<utility>`, but not `std::vector` or any other STL container for the underlying storage. - `T` is any object type: it need not be default-constructible, and its copy constructor may throw. Move-only types (e.g. `std::unique_ptr<U>`) should work. - `push_back` must be amortized $O(1)$; be prepared to justify your growth factor. - Single-threaded use; no thread-safety requirements. - Using the global `::operator new` / `::operator delete` for storage is acceptable (no allocator parameter required), but allocating storage must be separated from constructing elements. ### Clarifying Questions to Ask - Which subset of the `std::vector` API is in scope — just `push_back`/`operator[]`/`size`, or also `insert`, `erase`, and iterators? - What exception-safety guarantee should `push_back` provide — basic or strong (i.e., if an element's constructor throws mid-reallocation, is the vector left unchanged)? - Must the container support move-only element types, and types that are not default-constructible? - Is the global heap acceptable for storage, or do you want a pluggable allocator (`std::allocator_traits`-style)? - Which C++ standard may I target? (This affects `if constexpr`, `std::move_if_noexcept`, `std::construct_at`, aligned `operator new`, etc.) ### Part 1 Sketch the data layout of `Vector<T>` and implement `size()`, `capacity()`, `operator[]`, and the non-reallocating fast path of `push_back`. Then explain your growth policy when capacity is exhausted: what factor do you grow by, and why does that make `push_back` amortized $O(1)$? ```hint Layout Three members are enough: a pointer to the start of the storage plus a size and a capacity (the standard library typically stores three pointers — `begin`, `end`, `end-of-capacity` — which is equivalent). Size counts *constructed* elements; capacity counts *allocated slots*. ``` ```hint Why geometric growth If each reallocation multiplies capacity by a constant factor $g > 1$, the total number of element moves over $n$ insertions forms a geometric series bounded by a constant times $n$. Growing by a fixed *increment* instead makes `push_back` amortized $O(n)$. ``` #### What This Part Should Cover ```premium-lock What This Part Should Cover ``` ### Part 2 Now implement the memory management. Why is `new T[capacity]` the wrong way to allocate the buffer? Show how you allocate raw storage, construct elements into it, and destroy/deallocate correctly in the destructor — and implement the rule of five (copy constructor, move constructor, copy assignment, move assignment, destructor). ```hint What new T[n] would force `new T[n]` *constructs* `n` objects immediately — it requires `T` to be default-constructible and makes every slot's lifetime begin at allocation, so "capacity" and "size" can no longer differ. You need storage whose slots hold no objects until you explicitly create them. ``` ```hint The right primitives Allocate uninitialized bytes with `::operator new` (mind alignment for over-aligned `T`), create objects with placement new (or `std::construct_at`), and end their lifetimes with explicit destructor calls — in reverse order of construction — before `::operator delete`. ``` #### What This Part Should Cover ```premium-lock What This Part Should Cover ``` ### Part 3 Make `push_back` exception safe. Suppose `T`'s copy constructor can throw: what happens if it throws in the middle of relocating elements to a bigger buffer? Specify the guarantee your `push_back` provides and implement the reallocation path accordingly. When is it safe to *move* elements to the new buffer instead of copying them? ```hint Guarantee taxonomy Recall the three levels: basic (invariants hold, no leaks), strong (operation succeeds or the container is left exactly as it was), and nothrow. `std::vector::push_back` gives the strong guarantee — aim for that. ``` ```hint Order of operations Do all throwing work into the *new* buffer before touching the old one: construct the new element first, then relocate the old elements, and only then destroy the old buffer and swap pointers. If anything throws, destroy what you built in the new buffer, free it, and rethrow — the old state is untouched. ``` ```hint Move vs. copy Moving an old element out of the old buffer *mutates* it — if a later move throws, you cannot restore the original state. There is a standard utility that yields an rvalue only when `T`'s move constructor is `noexcept` (or `T` is not copyable), and an lvalue otherwise. ``` #### What This Part Should Cover ```premium-lock What This Part Should Cover ``` ### Part 4 Use type traits to optimize your implementation. Which traits let you (a) relocate the buffer with `memcpy` instead of element-by-element construction, (b) skip destructor calls entirely, and (c) decide between moving and copying during reallocation? Show where each applies in your code. ```hint The three traits One trait tells you a type can be byte-copied to a new location, one tells you its destructor does nothing, and one tells you its move constructor cannot throw. Pair each with `if constexpr` so the slow path is not even instantiated. ``` #### What This Part Should Cover ```premium-lock What This Part Should Cover ``` ### What a Strong Answer Covers ```premium-lock What a Strong Answer Covers ``` ### Follow-up Questions - How would you extend this to `insert` and `erase` in the middle, and what iterator/reference-invalidation rules fall out of your reallocation strategy? - How would you generalize the class to take an `Allocator` template parameter correctly, using `std::allocator_traits`? What changes in the move-assignment operator (think `propagate_on_container_move_assignment`)? - Why does `std::vector` not use a small-buffer optimization (as `std::string` typically does)? What would SBO break in your move constructor and invalidation guarantees? - Your Part 4 fast path uses `memcpy` on trivially copyable types. What does "trivially relocatable" mean, why isn't it in the standard type-trait set (as of C++17/20), and how do libraries like Folly's `fbvector` exploit it?

Quick Answer: This question evaluates mastery of C++ low-level container implementation, including raw memory allocation versus object construction, object lifetime and the rule of five, move and copy semantics, exception-safety guarantees, type-traits-driven optimizations, and amortized-growth analysis for push_back.

|Home/Software Engineering Fundamentals/Jump Trading

Implement a Simplified std::vector in C++: Growth, Memory, Exception Safety, and Type Traits

Jump Trading logo
Jump Trading
Sep 17, 2025, 12:00 AM
hardSoftware EngineerTake-home ProjectSoftware Engineering Fundamentals
2
0

Implement a simplified std::vector<T> from scratch in C++ — a dynamically growing array template. This is a classic C++ interview question at quantitative trading firms (asked frequently in C++ developer loops at top buy-side shops), and the interviewer is testing far more than the well-known "double the capacity" trick. A strong candidate is expected to demonstrate command of raw memory allocation vs. object construction, exception safety guarantees, and the use of type traits to optimize the implementation.

Your Vector<T> should support at minimum:

  • Default construction, destruction
  • Copy construction/assignment and move construction/assignment (the rule of five)
  • push_back (and ideally emplace_back ) with amortized O(1)O(1)O(1) growth
  • operator[] , size() , capacity() , and reserve()

You do not need iterators, insert/erase, or a custom allocator template parameter — but you must handle element type T correctly: T may be expensive to copy, may be move-only, may throw from its copy constructor, and may have a non-trivial destructor.

Constraints & Assumptions

  • Target C++17 or later; you may use <new> , <memory> , <type_traits> , and <utility> , but not std::vector or any other STL container for the underlying storage.
  • T is any object type: it need not be default-constructible, and its copy constructor may throw. Move-only types (e.g. std::unique_ptr<U> ) should work.
  • push_back must be amortized O(1)O(1)O(1) ; be prepared to justify your growth factor.
  • Single-threaded use; no thread-safety requirements.
  • Using the global ::operator new / ::operator delete for storage is acceptable (no allocator parameter required), but allocating storage must be separated from constructing elements.

Clarifying Questions to Ask Guidance

  • Which subset of the std::vector API is in scope — just push_back / operator[] / size , or also insert , erase , and iterators?
  • What exception-safety guarantee should push_back provide — basic or strong (i.e., if an element's constructor throws mid-reallocation, is the vector left unchanged)?
  • Must the container support move-only element types, and types that are not default-constructible?
  • Is the global heap acceptable for storage, or do you want a pluggable allocator ( std::allocator_traits -style)?
  • Which C++ standard may I target? (This affects if constexpr , std::move_if_noexcept , std::construct_at , aligned operator new , etc.)

Part 1

Sketch the data layout of Vector<T> and implement size(), capacity(), operator[], and the non-reallocating fast path of push_back. Then explain your growth policy when capacity is exhausted: what factor do you grow by, and why does that make push_back amortized O(1)O(1)O(1)?

What This Part Should Cover Premium

Part 2

Now implement the memory management. Why is new T[capacity] the wrong way to allocate the buffer? Show how you allocate raw storage, construct elements into it, and destroy/deallocate correctly in the destructor — and implement the rule of five (copy constructor, move constructor, copy assignment, move assignment, destructor).

What This Part Should Cover Premium

Part 3

Make push_back exception safe. Suppose T's copy constructor can throw: what happens if it throws in the middle of relocating elements to a bigger buffer? Specify the guarantee your push_back provides and implement the reallocation path accordingly. When is it safe to move elements to the new buffer instead of copying them?

What This Part Should Cover Premium

Part 4

Use type traits to optimize your implementation. Which traits let you (a) relocate the buffer with memcpy instead of element-by-element construction, (b) skip destructor calls entirely, and (c) decide between moving and copying during reallocation? Show where each applies in your code.

What This Part Should Cover Premium

What a Strong Answer Covers Premium

Follow-up Questions Guidance

  • How would you extend this to insert and erase in the middle, and what iterator/reference-invalidation rules fall out of your reallocation strategy?
  • How would you generalize the class to take an Allocator template parameter correctly, using std::allocator_traits ? What changes in the move-assignment operator (think propagate_on_container_move_assignment )?
  • Why does std::vector not use a small-buffer optimization (as std::string typically does)? What would SBO break in your move constructor and invalidation guarantees?
  • Your Part 4 fast path uses memcpy on trivially copyable types. What does "trivially relocatable" mean, why isn't it in the standard type-trait set (as of C++17/20), and how do libraries like Folly's fbvector exploit it?
Loading comments...

Browse More Questions

More Software Engineering Fundamentals•More Jump Trading•More Software Engineer•Jump Trading Software Engineer•Jump Trading Software Engineering Fundamentals•Software Engineer Software Engineering Fundamentals

Write your answer

Your first approved answer each day earns 20 XP.

Sign in to write your answer.
PracHub

Master your tech interviews with 8,500+ real questions from top companies.

Product

  • Questions
  • Learning Tracks
  • Interview Guides
  • Resources
  • Premium
  • For Universities

Browse

  • By Company
  • By Role
  • By Category
  • Topic Hubs
  • SQL Questions
  • AI Coding Questions
  • Compare Platforms
  • Discord Community

Support

  • support@prachub.com
  • (916) 541-4762

Legal

  • Privacy Policy
  • Terms of Service
  • About Us

© 2026 PracHub. All rights reserved.