std::STL Playground
0 / 21 topics opened
C++ Standard Template Library

Watch vector, map and sort move.

Every container and algorithm on this page runs live in your browser. Click an operation, watch the elements shift, then read the real C++ next to its real output. All code was compiled and run with g++ 13.3 using -std=c++17.

element iterator / touched empty slot or end() error / undefined behavior
vector: one contiguous block, index in O(1)
Playground · write and run your own C++

code editor

compiler GCC 13.3-std=c++17 -O2stdin supportedCtrl + Enter runs

Write any program here and run it. Start from the example below, pick one of the 37 topics from the list, or press Try it on any code block further down the page. Put your program's input in the stdin box, the way you would type it into a terminal.

Open in Compiler Explorer ↗
output
Press ▶ Run to compile.

            
Where your code runsRun sends your code to Compiler Explorer (godbolt.org), a free public compiler service, and shows what it prints. Don't paste passwords or private code.
  • Compile errors show in red, and the matching lines are marked in the editor.
  • Your draft stays in this browser, so a reload won't lose it. Reset brings back the selected example.
  • Programs are stopped after a few seconds, so an infinite loop shows "timed out" instead of hanging.
Basics · <cmath>

std::pow

#include <cmath>loop O(n)returns double

Raising a number to a power. The loop multiplies result by 2 nine times. pow(2, 9) does the same work but returns a double, so the answer is 512.0 printed as 512.

Doubles can round. A well-known case: some older MinGW builds printed 99 for (int)pow(10, 2). When you need an exact integer, use a loop, or 1LL << n for powers of two.

  • For large exponents with a modulus (common in contests), write fast exponentiation: square the base and halve the exponent each step. That runs in O(log n) instead of O(n).
  • 1 << 31 overflows a 32-bit int. Use 1LL << 31.
Basics · <utility>

std::pair

#include <utility>access O(1)

A pair stores two values in one variable. The two types can differ: pair<char, int> holds a letter and a number. Read them with .first and .second.

Pairs can nest. In pr4 the first slot is itself a pair, so you reach the 3 with pr4.first.first. Click an expression below to see which slot it reads.

  • Pairs compare by .first, then by .second. That is why sort() on a vector of pairs works without a comparator.
  • Pairs are how map stores each entry: it->first is the key, it->second the value.
Basics · <tuple>

std::tuple

#include <tuple>get<i> O(1)i fixed at compile time

A tuple is a pair that can hold any number of values. A student record of roll number, name and grade fits in tuple<int, string, char>. Read a value with get<0>(t), get<1>(t) and so on.

The index inside < > must be a constant. get<i>(t) with a loop variable i does not compile, because each slot can have a different type.

  • Tuples compare slot by slot, left to right. sort on a vector of tuples sorts by the first value, then the second, then the third.
  • For more than 3 or 4 fields, a small struct with named members reads better than get<3>.
Basics · every container

iterators

begin() / end()rbegin() / rend()*it reads the value

An iterator marks a position inside a container. it is the position, *it is the value stored there. begin() points at the first element. end() points at the slot after the last one, so a loop runs while i != end().

Reverse iterators flip the direction: rbegin() is the last element and rend() sits before the first. Step through both below. Try dereferencing end() to see why it is off limits.

Use != in loopsComparing with < only works for random-access iterators (vector, deque, arrays). A list, set or map iterator has no <, and i < ls.end() will not compile. != works everywhere.
Sequence container · dynamic array

std::vector

#include <vector>v[i] O(1)push_back O(1) amortizedinsert/erase front O(n)

A vector is an array that grows. Elements sit side by side in memory, so vec[i] jumps straight to slot i.

The dashed slots are capacity: memory already reserved but not used yet. When size == capacity and you push again, the vector allocates a bigger block and copies every element over. GCC doubles the capacity each time (1, 2, 4, 8, 16), which keeps push_back fast on average. Push until it happens and watch the capacity counter.

  • Know the final size? Call vec.reserve(n) first and skip every reallocation.
  • erase(begin()) shifts every remaining element left. On 1 million elements that is 1 million moves. Remove from the back when you can.
  • clear() sets size to 0 but keeps the capacity. The memory stays reserved.
  • vec.at(10) on a 4-element vector throws std::out_of_range. vec[10] does no check and reads whatever is in memory.
  • A reallocation moves the elements, so any iterator or pointer you saved into the old block is now invalid.
Sequence container · fixed size

std::array

#include <array>arr[i] O(1)size fixed at compile time

array<int, 5> is a plain C array int arr[5] with container manners: it knows its size(), has begin()/end(), supports at() with bounds checking, and can be copied with =.

The size is part of the type, so there is no push_back or erase. Use it when the count never changes: 12 months, 26 letters, 4 directions in a grid.

  • array<int, 5> a; inside a function leaves the values uninitialised, same as int a[5];. Write array<int, 5> a{}; to get zeros.
Sequence container · doubly linked list

std::list

#include <list>push/pop both ends O(1)insert at iterator O(1)find i-th O(n)

Each element lives in its own node, and each node points to the node before and after it (the ⇄ arrows). Adding at the front only rewires two pointers, which is why list has push_front and vector does not.

The price: there is no ls[3]. To reach the fourth element you walk from the head, one node at a time.

  • In practice a vector is often faster even for middle inserts on small data, because contiguous memory is cache friendly. Reach for list when you insert or splice at iterators you already hold.
  • list has its own ls.sort(). std::sort needs random access and will not compile on a list.
Sequence container · singly linked list

std::forward_list

#include <forward_list>push_front O(1)insert_after O(1)no size()

Each node points only to the next node, so it uses less memory than list. You can only move forward. Because a node can't see the one before it, you insert and erase after a position: insert_after, erase_after.

There is no push_back and no size(). Counting the nodes means walking all of them with distance(fl.begin(), fl.end()).

  • To insert at the very front with the "after" functions, use fl.before_begin(), a position that sits before the first node.
Sequence container · blocks of arrays

std::deque

#include <deque>push/pop both ends O(1)dq[i] O(1)

A deque (double-ended queue) stores elements in fixed-size blocks plus a small table that tracks the blocks. Pushing to the front fills the first block backwards. When a block is full, the deque adds a new block instead of moving everything.

You get fast pushes at both ends like a list, and dq[i] like a vector. Blocks of 4 below keep it readable; real GCC blocks are 512 bytes.

Common mix-upvector is one contiguous array. list is linked nodes. deque is a set of array blocks. A deque is not a linked list, and a list is not built on a vector.
Strings & bits · a container of chars

std::string

#include <string>s[i] O(1)find / substr O(n)

A string behaves like a vector<char> with text tools on top. push_back, size, iterators, sort and reverse all work.

Watch the arguments. substr(start, length) takes a length, not an end index, so s.substr(0, 5) gives 5 characters. find returns the index of the first match, or string::npos when there is none.

npos is not -1 you can compare to intstring::npos is the largest size_t value. Write if (s.find(x) == string::npos). Storing the result in an int and checking < 0 is fragile and warns on most compilers.
Strings & bits · fixed number of bits

std::bitset

#include <bitset>set / test O(1)count O(N/64)

bitset<8> stores 8 on/off flags packed into one byte. Bit 0 is the rightmost when printed. set(i) turns bit i on, reset(i) turns it off, flip(i) toggles it, and count() tells you how many are on.

Click any bit below to flip it and watch the decimal value change.

  • A bitset<1000000> takes 125 KB. A vector<bool> of the same size is similar, a vector<int> takes 4 MB.
  • The size must be a compile-time constant. For a size known only at runtime, use vector<bool>.
Container adapter · LIFO

std::stack

#include <stack>push / pop / top O(1)built on deque

Last in, first out. You only touch the top: push puts an element on top, top() reads it, pop() removes it. There is no iteration and no indexing, so to print a stack you pop it until empty().

The example pushes 5, 8, 6, 3 and prints 3 6 8 5. Run the while loop below to see why the order flips.

Undefined behaviorCalling top() or pop() on an empty stack is not a guaranteed runtime error. It may crash, print garbage, or appear to work and break later. Check !st.empty() first. The button above shows what would happen.
  • Real uses: undo history, matching brackets in "(a[b]{c})", depth-first search without recursion.
Container adapter · FIFO

std::queue

#include <queue>push / pop / front / back O(1)

First in, first out, like a line at a ticket counter. push joins at the back, pop leaves from the front. front() and back() read the two ends.

  • Breadth-first search uses a queue: visit a node, push its neighbours, pop the next node. The shortest path in an unweighted grid falls out of that order.
Container adapter · binary heap

std::priority_queue

#include <queue>top O(1)push / pop O(log n)

A priority_queue is a binary heap stored inside a vector. Index i has children at 2i+1 and 2i+2. In a max-heap every parent is at least as large as its children, so the maximum always sits at index 0.

push adds the value at the end and swaps it upward while it beats its parent. pop moves the last element to the root and swaps it downward. Each move climbs one level, and a heap of n elements has about log₂ n levels. With 1,000,000 elements that is 20 swaps at most.

  • greater<int> flips the order and makes a min-heap. You must also pass the underlying container, hence priority_queue<int, vector<int>, greater<int>>.
  • Dijkstra's shortest path uses a min-heap of pair<dist, node>.
Ordered associative · red-black tree

std::set

#include <set>insert / erase / find O(log n)unique + sorted

A set keeps unique values in ascending order. Insert 12 twice and the second insert is ignored. Internally it is a balanced binary tree, so each operation walks one path from root to leaf: about 20 steps for a million elements.

lower_bound(x) returns the first element ≥ x. upper_bound(x) returns the first element > x. If nothing qualifies you get end(). Try 11, 12 and 214 below.

Check before *upper_bound(214) on this set returns end(). Printing *ub before checking ub == st.end() reads past the tree: undefined behavior. The fixed code checks first.
  • Use the member st.lower_bound(x), not std::lower_bound(st.begin(), st.end(), x). The free function is O(n) on a set because set iterators can't jump.
  • erase(it1, it2) removes the half-open range: it1 is removed, it2 is kept.
Ordered associative · duplicates allowed

std::multiset

#include <set>insert / find O(log n)count O(log n + k)

Same as set, but duplicates stay. Values are still sorted, and equal values sit next to each other.

The trap is erase. ms.erase(2) with a value removes every 2. ms.erase(ms.find(2)) with an iterator removes one. Try both.

  • A multiset works as a sorted bag for "sliding window median" or "remove one copy of the largest" problems: ms.erase(prev(ms.end())).
Ordered associative · key → value

std::map

#include <map>[] / insert / find O(log n)sorted by key

A map stores key-value pairs with unique keys, sorted by key. It is the same tree as a set, with a value attached to each key.

Three behaviors catch people out, and all three are buttons below. mpp[k] = v overwrites an existing value. mpp.insert({k, v}) does not overwrite when the key exists. Reading mpp[k] for a missing key creates it with an empty value.

  • To check for a key without creating it, use mpp.count(k), mpp.find(k) != mpp.end(), or mpp.contains(k) in C++20.
  • Counting words: freq[word]++ works because a missing key starts at 0.
Ordered associative · duplicate keys

std::multimap

#include <map>insert O(log n)equal_range O(log n)no [] operator

A multimap allows the same key many times. Entries are sorted by key only. Entries with equal keys keep the order you inserted them, which is why the output shows 1 -> a, 1 -> b, 1 -> a.

equal_range(k) returns a pair of iterators. first points at the first entry with key k. second points one past the last one. Loop from first while i != second.

  • A multimap has no []: with several values per key, mm[2] would be ambiguous.
Unordered associative · hash table

std::unordered_set

#include <unordered_set>insert / find O(1) averageworst O(n)

Values go into buckets. A hash function turns the value into a bucket number, so a lookup computes one number and checks only that bucket. No tree to walk, which makes the average cost O(1).

This model uses x % 7 so you can predict where each value lands. GCC hashes an int to itself and then takes value % bucket_count, so the idea matches. Because buckets follow the hash, iteration order looks random, and there is no lower_bound.

  • Worst case: when many values land in one bucket, lookups scan a long chain. Try inserting 7, 14, 21, 28 to see a single bucket fill up.
  • Iteration order differs between compilers. Never rely on it.
  • unordered_multiset and unordered_multimap exist too: same buckets, duplicates allowed.
Unordered associative · hash table

std::unordered_map

#include <unordered_map>[] / find O(1) averageworst O(n)

A map built on a hash table. The key decides the bucket, the value rides along. You get the same [], insert, find and count as map, faster on average, with no ordering. This model hashes the key with key % 5.

The output of the example came out as 4, 3, 2, 1 on g++ 13.3. Another compiler can print a different order.

  • Use map when you need keys in order or range queries. Use unordered_map for plain lookups like frequency counts. pair keys don't have a default hash, so unordered_map<pair<int,int>, int> needs a custom hash.
Algorithm · <algorithm>

std::sort

#include <algorithm>O(n log n)range is [first, last)

sort(first, last) sorts the half-open range: first is included, last is not. So sort(nums + 1, nums + 4) sorts indices 1, 2 and 3 and leaves 0 and 4 alone. The result is 9 1 3 4 0.

For an array, nums + k is a pointer to index k. For a vector, use vec.begin() + k. Same idea.

  • GCC's std::sort is introsort: quicksort that switches to heapsort if recursion gets too deep, and insertion sort for small pieces. It is not stable. Use stable_sort when equal elements must keep their order.
Sorting · keeps ties in order

std::stable_sort

#include <algorithm>O(n log n)extra memory O(n)

Two students both scored 90. A stable sort promises that whoever came first in the input still comes first in the output. std::sort makes no such promise and may swap them.

This matters when you sort twice: sort by name first, then stable_sort by marks, and students with equal marks stay alphabetical.

Sorting · sort only what you need

std::nth_element & partial_sort

#include <algorithm>nth_element O(n) avgpartial_sort O(n log k)

nth_element(first, first + k, last) puts the value that belongs at index k into index k. Everything left of it is ≤, everything right is ≥, and neither side is sorted. That is enough for a median or the k-th smallest score, and it runs in linear time.

partial_sort(first, first + k, last) sorts only the first k positions. Use it for "top 3 scores" when fully sorting 1,000,000 values would waste time.

Custom ordering · sort(first, last, comp)

comparators

comp(a, b) → true means a goes firstmust be strict

A comparator answers one question: should a come before b? sort calls it many times and arranges elements from the answers.

complexComparator sorts pairs by second descending, and breaks ties by first ascending. Click any two pairs below to ask the comparator about them, then sort.

Never return true for equal elementsA comparator written as if (a < b) return false; else return true; returns true when a == b. That breaks the rule sort relies on (comp(a, a) must be false). With enough duplicates, std::sort can run past the end of the array and crash. Write return a > b; instead.
Algorithm · linear scan

std::count & find

#include <algorithm>O(n)

Both walk the range from left to right. count checks every element and returns how many match. find stops at the first match and returns an iterator to it. When nothing matches, find returns last (here nums + 5), so compare against that before using *it.

  • On a set or map, call the member st.find(x). It is O(log n). std::find on a set still scans every element.
Searching · sorted ranges

std::binary_search

#include <algorithm>O(log n)range must be sorted

Binary search looks at the middle, throws away the half that can't contain the answer, and repeats. 1,000,000 elements need at most 20 looks.

binary_search only answers yes or no. lower_bound (first ≥ x) and upper_bound (first > x) return positions, and ub - lb counts how many times x appears. Step through the search below and watch lo, mid and hi close in.

Unsorted inputOn an unsorted vector these functions still run and return something. The answer is just wrong. Sort first, or keep the data in a set.
Searching · with a condition

count_if, find_if, all_of, any_of, none_of

#include <algorithm>O(n)takes a lambda

These take a condition instead of a value. You write the condition as a lambda: [](int x) { return x % 2 == 0; } is a small unnamed function that returns true for even numbers.

all_of, any_of and none_of stop as soon as the answer is known. Pick a condition and run each one to see how many elements it actually checks.

  • all_of on an empty range returns true, any_of returns false. There is nothing to break the rule, and nothing to satisfy it.
Algorithm · <algorithm>

std::max_element & reverse

#include <algorithm>O(n)

max_element and min_element return an iterator, not a value. Use *it for the value and it - nums for the index. reverse swaps elements from both ends toward the middle.

  • Need both at once? minmax_element returns a pair of iterators in a single pass.
  • For a handful of plain values, max({3, 9, 4}) takes an initializer list and returns 9. No array needed.
Modifying · shift with wrap-around

std::rotate

#include <algorithm>O(n)

rotate(first, middle, last) moves middle to the front and wraps the earlier elements to the back. rotate(v.begin(), v.begin() + 2, v.end()) turns 1 2 3 4 5 6 into 3 4 5 6 1 2: a left rotation by 2.

For a right rotation, use reverse iterators: rotate(v.rbegin(), v.rbegin() + k, v.rend()).

  • If k can be larger than the size, use k % v.size() first. v.begin() + 8 on 6 elements is out of range.
Modifying · remove adjacent duplicates

std::unique

#include <algorithm>O(n)does not change size

unique slides each new value forward over the repeats and returns an iterator to the new logical end. It does not shrink the vector. The leftover tail still exists with unspecified values until you erase it.

It also only compares neighbours, so sort first. The full idiom is sort, then v.erase(unique(v.begin(), v.end()), v.end()).

Modifying · set many values at once

std::fill & iota

#include <algorithm>#include <numeric>O(n)

fill(first, last, value) writes the same value into every slot of the range. It is the standard way to reset a DP table to -1.

iota(first, last, start) writes start, start + 1, start + 2 and so on. Use it to build the index list 0 1 2 … n-1 that you then sort by some key.

  • memset(dp, -1, sizeof dp) works for -1 and 0 only, because it sets bytes. fill works for any value.
Modifying · apply a function to every element

std::transform & for_each

#include <algorithm>O(n)

transform reads each input element, calls your function, and writes the result to an output range. The output can be a different vector or the same one.

for_each just calls your function on each element. To change the elements, the lambda must take a reference: [](int &x) { x *= 10; }. Without the & it changes a copy and the vector stays the same.

  • The output range must already have room. vector<int> sq(v.size()) makes that room; an empty vector plus back_inserter(sq) also works.
Algorithm · lexicographic order

std::next_permutation

#include <algorithm>one step O(n)all n! orders

next_permutation rearranges the range into the next larger order, the way words follow each other in a dictionary. It returns false after the largest order (cba) and resets the range to the smallest (abc). That is why the do { } while loop prints each permutation once.

Start sorted or you miss some: starting from bca prints only bca cab cba. prev_permutation walks backwards, so start it from descending order.

  • 3 letters give 6 orders. 10 letters give 3,628,800. Brute force over permutations only works for small n, around 10 or less.
Algorithm · <numeric>

std::accumulate

#include <numeric>O(n)

accumulate(first, last, init) starts at init and adds each element. With init 0 you get the plain sum, 16. With init 5 you get 21.

The type of init sets the type of the running total. Summing a vector<long long> with init 0 adds into an int and can overflow. Write 0LL.

Name clashNaming your own function accumulate() while using namespace std; is active compiles here, but it is fragile and confusing to read. The snippet renames it to accumulateDemo.
Numeric · prefix sums

std::partial_sum

#include <numeric>build O(n)range sum query O(1)

partial_sum writes running totals: pre[i] = v[0] + … + v[i]. After that, the sum of any range v[l..r] is pre[r] - pre[l - 1], one subtraction instead of a loop.

With 100,000 queries on 100,000 numbers, a loop per query is 10¹⁰ steps. Prefix sums make it 200,000.

  • adjacent_difference is the reverse: it recovers v from its running totals, and it is the basis of difference arrays for range updates.
Numeric · <numeric>

std::gcd & lcm

#include <numeric>O(log min(a,b))C++17

gcd uses Euclid's rule: gcd(a, b) = gcd(b, a % b), stopping when b is 0. 48 and 18 take three steps: 48 % 18 = 12, 18 % 12 = 6, 12 % 6 = 0.

lcm(a, b) equals a / gcd(a, b) × b. Dividing first keeps the intermediate number small.

  • __gcd is a GCC extension from before C++17. It won't compile on MSVC. Prefer std::gcd.
Set algorithms · two sorted ranges

set_union, set_intersection, set_difference

#include <algorithm>O(n + m)inputs must be sorted

These walk two sorted ranges with one pointer each. At every step they compare the two current values, emit something, and advance the smaller side. No hashing, no nested loops.

The output goes through an iterator. back_inserter(u) calls u.push_back for each result, so you don't need to size u in advance.

  • They work on vectors and on sets. They do not work on unordered containers, because those are not sorted.
Reference

complexity cheat sheet

n is the number of elements. "amortized" means fast on average, with an occasional slow step (a vector reallocating). "avg" means average case for hash tables.

ContainerAccess i-thSearchInsertEraseOrdered?Duplicates?
vectorO(1)O(n)back O(1)* · middle O(n)back O(1) · middle O(n)insertion orderyes
arrayO(1)O(n)fixed sizefixed sizeindex orderyes
dequeO(1)O(n)ends O(1) · middle O(n)ends O(1) · middle O(n)insertion orderyes
listO(n)O(n)O(1) at iteratorO(1) at iteratorinsertion orderyes
forward_listO(n)O(n)O(1) after iteratorO(1) after iteratorinsertion orderyes
stringO(1)find O(n·m)back O(1)* · middle O(n)back O(1) · middle O(n)text orderyes
bitset<N>O(1)count O(N/64)fixed N bitsfixed N bitsbit indexon/off
stack / queuetop/front onlynoO(1)O(1)LIFO / FIFOyes
priority_queuetop only O(1)noO(log n)O(log n)max (or min) on topyes
set / mapO(n)O(log n)O(log n)O(log n)sortedno
multiset / multimapO(n)O(log n)O(log n)O(log n + k)sortedyes
unordered_set / mapnoO(1) avgO(1) avgO(1) avgno orderno

Which container should you pick?

You needUseExample
a size that never changesarraydays in each of 12 months
a growing list, index accessvectormarks of 60 students, read marks[i]
add/remove at both endsdequesliding window maximum
undo, bracket matchingstackis "{[()]}" balanced?
process in arrival orderqueueBFS on a grid, print jobs
always grab the largest/smallestpriority_queueDijkstra, top-k scores
unique values, sorted, range queriesset"smallest value ≥ 50" with lower_bound
fast "have I seen this?"unordered_setduplicate detection in 10⁶ numbers
key → value, sorted keysmaproll number → name, printed in order
key → value, just lookupsunordered_mapword frequency count
many on/off flagsbitsetsieve of primes up to 10⁶
range sums, many queriespartial_sumsales between day l and day r
k-th smallest or mediannth_elementmedian of 10⁶ salaries
Reference

common mistakes

Each of these compiles. Most still print something. That is what makes them dangerous.

Reference

check yourself

Eleven quick questions. Each answer explains itself when you click.