Modern C++ Essentials for Interviews
This is a practical reference for the C++ features you actually use when whiteboarding or writing code in an interview. Every section ends with a "when to use this" takeaway so you can reach for the right tool without thinking.
1. Header and Boilerplate
#include <bits/stdc++.h>
This is a GCC-specific header that pulls in every standard library header in one shot. It is non-standard (does not work on MSVC, and technically not portable), but it is universally accepted in competitive programming and interview settings because it saves you from remembering which header contains std::priority_queue vs std::unordered_map.
#include <bits/stdc++.h>
using namespace std;
int main() {
ios_base::sync_with_stdio(false);
cin.tie(nullptr);
// your code
return 0;
}using namespace std;
In production code this is considered a smell because it drags every standard name into the global namespace and can cause ambiguity (e.g. std::count vs your own count). In an interview it is fine and expected — interviewers care about your algorithm, not your namespace hygiene.
When to use in interviews: always. Start every file with the boilerplate above and focus on the problem.
2. Fast I/O
Standard cin/cout are slow because they are synchronized with C's stdio by default and because cin is flushed and tied to cout so that prompts appear before reads.
ios_base::sync_with_stdio(false); // untie from C stdio (~10x speedup)
cin.tie(nullptr); // stop flushing cout on every cin readAfter this, do not mix printf/scanf with cin/cout in the same program.
getline for full lines
cin >> s stops at whitespace. For a whole line of text (including spaces), use getline.
string line;
getline(cin, line);
// If you previously did cin >> n, there is a leftover '\n' in the buffer:
cin >> n;
cin.ignore(); // discard the newline
getline(cin, line); // now reads the real next lineReading until EOF
int x;
while (cin >> x) {
// process x
}
// Or line-by-line:
string line;
while (getline(cin, line)) {
// process line
}endl vs "\n"
endl writes a newline and flushes the buffer. "\n" just writes a newline. In tight output loops endl can be 100x slower.
cout << x << "\n"; // preferred
cout << x << endl; // only when you actually need a flushWhen to use in interviews: add the fast I/O lines whenever you have heavy input (thousands+ of reads). Always prefer "\n" over endl.
3. Modern C++ Features (C++11/14/17)
auto keyword
auto asks the compiler to deduce the variable's type from its initializer. It is invaluable for iterator and lambda types that would otherwise be unwieldy.
auto it = mp.find(key); // instead of unordered_map<string,int>::iterator
auto n = v.size(); // size_t
auto sq = [](int x) { return x*x; }; // lambda type is anonymousDo not use auto when the type is short and the initializer is ambiguous. auto x = 0; is clear; auto x = foo(); may hide whether foo returns int or double.
Range-based for loop
vector<int> v{1,2,3};
for (int x : v) { /* copy */ }
for (int& x : v) { /* reference — can modify */ }
for (const auto& x : v) { /* const ref — no copy, read only (best default) */ }For map/unordered_map:
for (const auto& [key, value] : mp) { // C++17 structured binding
cout << key << " -> " << value << "\n";
}nullptr vs NULL
NULL is typically a macro for 0, so f(NULL) may call an int overload. nullptr is a real pointer type (std::nullptr_t) and always resolves to a pointer overload. Use nullptr everywhere.
Uniform initialization {} vs ()
vector<int> a(5, 0); // vector of 5 zeros
vector<int> b{5, 0}; // vector of two elements: 5 and 0 ← gotcha
int x{3.14}; // error: narrowing conversion
int y(3.14); // silently truncates to 3{} prevents narrowing and is preferred for aggregate types, but for containers like vector it forwards to the initializer_list constructor and can surprise you.
constexpr
Computed at compile time when possible. Useful for sizes and mathematical constants.
constexpr int MOD = 1'000'000'007;
constexpr int N = 1 << 20;
int fib[N];Structured bindings (C++17)
Destructure pairs, tuples, structs, and arrays.
pair<int,int> p{1, 2};
auto [a, b] = p;
map<string,int> mp;
for (auto& [key, value] : mp) { ... }std::move and rvalue references
std::move casts an lvalue to an rvalue so its resources can be stolen instead of copied. You rarely need to write it yourself in interview code, but recognizing it matters.
string s = "hello world";
vector<string> v;
v.push_back(std::move(s)); // s is now empty; no copy performedWhen to use in interviews: auto for iterators and complex types, auto& in range-for to avoid copies, nullptr always, structured bindings when iterating maps.
4. Lambdas
Basic syntax
[capture](parameters) -> return_type { body }The return type can almost always be omitted — the compiler deduces it.
auto add = [](int a, int b) { return a + b; };
int s = add(2, 3); // 5Capture modes
| Capture | Meaning |
|---|---|
[] | Capture nothing |
[=] | Capture all used variables by value |
[&] | Capture all used variables by reference |
[x] | Capture x by value |
[&x] | Capture x by reference |
[=, &x] | Everything by value except x by reference |
[&, x] | Everything by reference except x by value |
[this] | Capture the enclosing object's this pointer |
int offset = 10;
auto shift = [offset](int x) { return x + offset; }; // by value — safe
auto track = [&offset](int x) { offset += x; }; // modifies outer offsetCapturing by reference and letting the lambda outlive the referenced variable is a classic dangling-reference bug.
Lambdas with STL algorithms
vector<pair<int,string>> v = {{3,"c"},{1,"a"},{2,"b"}};
sort(v.begin(), v.end(), [](const auto& a, const auto& b) {
return a.first < b.first; // ascending by first
});
int count = count_if(v.begin(), v.end(), [](const auto& p) {
return p.first > 1;
});Recursive lambdas with std::function
A lambda has an anonymous type, so it can't refer to itself directly. Wrap it in std::function or pass itself as a parameter.
function<int(int)> fact = [&](int n) -> int {
return n <= 1 ? 1 : n * fact(n - 1);
};
// Alternative (C++14, slightly faster — no std::function overhead):
auto dfs = [&](auto&& self, int node) -> void {
for (int nb : adj[node]) self(self, nb);
};
dfs(dfs, root);Generic lambdas (C++14)
auto parameters make a lambda a template.
auto print = [](const auto& x) { cout << x << "\n"; };
print(42);
print("hello");
print(3.14);When to use in interviews: custom comparators for sort/priority_queue, inline DFS/BFS helpers, predicate arguments for count_if/find_if.
5. Utility Types
pair
pair<int,string> p{1, "one"};
cout << p.first << " " << p.second;
auto q = make_pair(2, "two"); // auto-deduces types
auto [num, name] = p; // structured bindingPairs are ordered lexicographically by default, which is convenient for sorting "by key then value".
tuple
tuple<int,string,double> t{1, "a", 3.14};
cout << get<0>(t) << " " << get<1>(t) << "\n";
int x; string s; double d;
tie(x, s, d) = t; // unpack
auto [a, b, c] = t; // C++17 structured bindingoptional (C++17)
Represents "maybe a value", replacing sentinel return values like -1.
optional<int> find_index(const vector<int>& v, int target) {
for (int i = 0; i < (int)v.size(); ++i)
if (v[i] == target) return i;
return nullopt;
}
auto idx = find_index(v, 5);
if (idx) cout << *idx << "\n"; // or idx.value()string_view (C++17)
A non-owning view into a string. Passing string_view avoids copies when a function only needs to read.
int count_vowels(string_view s) {
int c = 0;
for (char ch : s) if (string_view("aeiou").find(ch) != string_view::npos) ++c;
return c;
}
count_vowels("hello"); // no allocation
count_vowels(some_string); // also no copyWhen to use in interviews: pair/tuple for multi-value returns; optional when "not found" is a real case; string_view rarely needed but worth knowing.
6. Numeric Types and Limits
| Type | Size | Range (approx) | Use when |
|---|---|---|---|
int | 32-bit | ±2.1 × 10^9 | default integer |
long long | 64-bit | ±9.2 × 10^18 | sums/products might overflow |
size_t | 64-bit unsigned (usually) | 0 to 1.8 × 10^19 | container sizes, indices |
double | 64-bit | 15-17 significant digits | floats; avoid equality compares |
Limits
INT_MAX // 2,147,483,647
INT_MIN // -2,147,483,648
LLONG_MAX // 9,223,372,036,854,775,807
LLONG_MIN
numeric_limits<int>::max();
numeric_limits<double>::infinity();Integer overflow — the classic bug
int a = 100000, b = 100000;
long long bad = a * b; // WRONG — int*int overflows, then widens
long long good = (long long)a * b; // cast one operand firstAny time you multiply two ints whose product could exceed ~2 × 10^9, cast to long long before the multiply.
When to use in interviews: default to int; switch to long long the moment you see sums of N ≤ 10^5 elements up to 10^9, or any product of ints.
7. String Operations
string s = "hello";
s.size(); // 5
s.length(); // same as size
s.empty(); // false
s.substr(1, 3); // "ell" — start, length
s.find("ll"); // 2, or string::npos if not found
s + " world"; // "hello world"
s += '!'; // append a charConversions
string s = to_string(42); // "42"
int n = stoi("42"); // 42
long long ll = stoll("10000000000");
double d = stod("3.14");
// char digit to int:
char c = '7';
int d2 = c - '0'; // 7
// lowercase / uppercase:
for (char& ch : s) ch = tolower(ch);Reverse
string t = s;
reverse(t.begin(), t.end());Splitting a string
vector<string> split(const string& s, char delim) {
vector<string> out;
stringstream ss(s);
string tok;
while (getline(ss, tok, delim)) out.push_back(tok);
return out;
}When to use in interviews: keep this file of tricks handy — stoi, to_string, substr, and c - '0' appear in almost every string problem.
8. References vs Pointers vs Values
| Pass by | Copies? | Can modify? | Typical use |
|---|---|---|---|
| value | yes | local copy | small POD (int, char, small structs) |
const T& | no | no | default for everything else |
T& | no | yes | function needs to mutate caller's data |
T* | no | yes (if non-const) | pointer can be null; ownership contexts |
void process(const vector<int>& v); // read-only, no copy
void sort_inplace(vector<int>& v); // mutates caller
for (const auto& x : bigVec) // no copies in the loop
for (auto& x : bigVec) // mutate in place
for (auto x : bigVec) // copies each element — usually wastefulWhen to use in interviews: function params → const T& unless you need to mutate; range-for → const auto& unless you need to mutate.
9. Common Gotchas (CRITICAL)
These bite constantly. Commit them to memory.
Integer overflow on n * n
int n = 100000;
long long sq = n * n; // WRONG — computed as int, overflows
long long sq2 = 1LL * n * n; // right: promotes to long long
long long sq3 = (long long)n * n; // also rightIterator invalidation
vector<int> v = {1,2,3};
auto it = v.begin();
v.push_back(4); // may reallocate — `it` is now dangling
*it; // undefined behaviorInserting into or erasing from a vector can invalidate every iterator. For map/set, erase only invalidates the erased iterator.
Modifying a container while iterating
for (auto it = v.begin(); it != v.end(); ++it) {
if (*it == 0) v.erase(it); // WRONG — `it` invalidated, loop skips/UB
}
// Correct:
for (auto it = v.begin(); it != v.end(); ) {
if (*it == 0) it = v.erase(it); // erase returns next valid iterator
else ++it;
}
// Or use erase-remove:
v.erase(remove(v.begin(), v.end(), 0), v.end());auto drops references
vector<int> v = {1,2,3};
for (auto x : v) x *= 2; // modifies a copy; v unchanged
for (auto& x : v) x *= 2; // modifies vsize_t underflow
v.size() returns an unsigned size_t. Subtracting from an empty vector's size wraps to a huge positive number.
vector<int> v;
for (size_t i = 0; i < v.size() - 1; ++i) { ... } // infinite loop if empty
// Safer:
for (int i = 0; i + 1 < (int)v.size(); ++i) { ... }map[key] inserts a default
unordered_map<string,int> mp;
if (mp["missing"] == 0) { ... } // this INSERTS "missing" with value 0
if (mp.count("missing")) { ... } // safe: does not insert
if (mp.find("missing") != mp.end()) { ... } // also safeThis can silently change your map's size and ruin iteration bounds.
unordered_map worst-case O(n)
With adversarial inputs (or pathological hashing of int), unordered_map can degenerate to O(n) per op. Competitive judges sometimes exploit this. In interviews it is rarely an issue, but if a solution "should work" and TLEs, consider switching to map (guaranteed O(log n)) or adding a custom hash with a random seed.
10. Useful Built-in Functions
GCC provides several __builtin_* intrinsics that map directly to fast CPU instructions.
__builtin_popcount(x); // number of set bits in unsigned int
__builtin_popcountll(x); // same, for unsigned long long
__builtin_clz(x); // count leading zeros (x must be != 0)
__builtin_ctz(x); // count trailing zeros
__builtin_parity(x); // parity of set bits
__gcd(a, b); // greatest common divisor (C++17: use std::gcd)
__lg(x); // floor(log2(x)) — integer log
swap(a, b); // O(1) for most built-ins and move-aware types
min({1, 5, 3, 2}); // 1 — initializer list overload
max({a, b, c, d});Standard alternatives that work on any compiler:
#include <numeric>
gcd(a, b); // C++17
lcm(a, b); // C++17When to use in interviews: __builtin_popcount for bit manipulation problems, __gcd for number theory, min({...})/max({...}) to avoid chained calls, swap for in-place algorithms.
Quick Reference Boilerplate
Paste this at the top of every interview solution:
#include <bits/stdc++.h>
using namespace std;
int main() {
ios_base::sync_with_stdio(false);
cin.tie(nullptr);
// your solution
return 0;
}