Concepts / Unordered Containers and Hash Tables in C++

Unordered Containers and Hash Tables in C++

Standard library containers are organized into families, and the family tells you whether elements are reached by position, by key, or through hashed lookup.

  • Programming
Interactive lab

Try it: How a dict Stores Keys

How a dictionary turns a key into a slot with a hash, what happens on a collision, why deleted keys leave a marker, and why the table grows.

How it works

  1. hash(key) gives a number; hash % table size gives the key's home slot.
  2. If that slot is taken by another key (a collision), probe the next slot.
  3. Deleting leaves a DELETED marker so lookups keep probing past it.
  4. When more than 2/3 of the slots are used, the table doubles and every key is re-inserted.

Default run (5 steps): A dict with 3 keys in 8 slots. Choose an operation. Example: d["mango"] = 5. … Done. 4 keys in 8 slots.

Simplified: Uses a small educational hash (Python's real string hash is randomised per run) and simple linear probing; CPython's probe order is more elaborate.

Educational simulation

Loading the simulation…

Choosing a Container Before You Write Code

Before you write a program that stores a collection of values, you have to decide what kind of collection you need. Should the items stay in the order you added them? Should you look items up by a name or label? Should duplicates be allowed? These questions shape the whole design, and the C++ standard library answers them with a set of ready-made container types. You do not have to build each storage structure yourself.

The library is designed around the idea that you pick the container that best fits the needs of your application. This article walks through the containers by asking what each one is good at, so that a list of names becomes a set of choices you can reason about. The most useful question to ask first is how you intend to reach an element: by its position in a line, or by a key that identifies it.

Following a Key Lookup Step by Step

Start with the behavior of a keyed container. You store a value under a key, and later you give the same key to retrieve that value. The diagram below traces this for a small generated example: a container that maps fruit names to counts. Each key appears once, and each key leads to exactly one value. The keys and values here are an illustration of the idea and are not data from the source.

applekey3value for applepearkey7value for pearplumkey2value for plummaps tomaps tomaps to
When you give the key pear, the container returns the value stored under pear. The key is the handle you use to reach the element.

The diagram shows what a lookup means: you supply the key pear and receive 7. The source does not describe how an unordered container internally arrives at the place where a value is stored, so this article does not describe that step either. What you can rely on is the stated purpose: unordered containers are built on hash tables and are optimized for lookup, often with a string key.

cpp
Output
7
Notes on the code
  • unordered_map<K,V> pairs a key of type K with a value of type V
  • Assigning with a key stores the value under that key
  • Reading with the same key retrieves the stored value

Three Families of Standard Containers

The standard containers fall into families, and the family tells you how elements are arranged and how you reach them. Sequential containers keep elements in a sequence, so position is the main way to think about them. Associative containers store elements together with keys, so you reach a stored item by its key. Unordered containers are also keyed, but they are built on hash tables and are optimized for lookup. The diagram places the main families under the single idea of a standard container.

Standard containersSequentialreached by positionvector, list,forward_list, dequeAssociativereached by keyset, multiset, map,multimapUnorderedkeyed, hash table lookupunordered_map,unordered_multimap,unordered_set,unordered_multisetfamilyfamilyfamilymembersmembersmembers
The three families all fall under the idea of a standard container. Sequential containers are reached by position, associative containers by key, and unordered containers by key through hashed lookup.

Position Versus Key Access

Now trace what changes when you finish storing data and go to find it. In an order-based sequential container you locate an element by where it sits in the line. In a key-based container you locate an element by the key that was attached to it when it was stored. The comparison below sets these two ways of reaching data side by side.

Positionhow you find an elementvector<T>sequentialKeyhow you find an elementmap<K,V>associativedeque<T>sequentialunordered_map<K,V>unordered, hash table
Order-based containers answer a question about position in the sequence. Key-based containers answer a question about the key that identifies a stored item. The unordered containers are a key-based family optimized for lookup.

Two things follow from the source's description. A key-based container is the right choice when your question is which value belongs to this name or label. An order-based container is the right choice when your question is what comes first, what comes next, or what is at a given place in the line. The source's description of unordered containers stresses lookup rather than ordering, so do not assume they organize elements the way a sequence does.

Set, Map, and Their Multi and Unordered Versions

The keyed families come in matching pairs. Each associative container has an unordered counterpart with the same set-or-map and single-or-multiple distinctions. The table below lines these up so you can see which rule each name relaxes.

ContainerFamilyWhat each element holdsRelaxed rule
set<T>AssociativeJust a key, with no separate valueBase container
multiset<T>AssociativeJust a keyA value may occur many times
map<K,V>AssociativeA key of type K paired with a value of type VBase container
multimap<K,V>AssociativeA key of type K paired with a value of type VA key may occur many times
unordered_set<T>UnorderedJust a key, looked up through hashingHashed lookup instead of associative lookup
unordered_multiset<T>UnorderedJust a key, looked up through hashingA value may occur many times
unordered_map<K,V>UnorderedA key of type K paired with a value of type VHashed lookup instead of associative lookup
unordered_multimap<K,V>UnorderedA key of type K paired with a value of type VA key may occur many times

Each unordered container mirrors an associative one. The source describes the relaxed rules; the 'Relaxed rule' column for unordered containers follows from the source's statement that they use hashed lookup.

When you choose among these, ask two separate questions. First, does each item need a separate value, or is the key the whole item? Second, may the same key or value appear more than once? The family question, whether you want associative or unordered, is a third question that depends on whether fast lookup by key is your main job.

Adaptors and Container-Like Types

The standard library also provides container adaptors, named queue<T>, stack<T>, and priority_queue<T>. An adaptor is a class that provides a different interface to a container. It does not present the operations of a general-purpose collection. Instead, it presents a narrower set of operations suited to a particular access pattern. The adaptors sit alongside the containers as a distinct category, each offering its own interface to underlying storage. The source tells you the adaptors exist, gives their names, and advises you to look them up when you need them.

Some types behave like containers without fitting neatly into the three families or the adaptors. The array<T,N> is a fixed-size array, so its size is set as part of its type rather than growing as elements are added. The bitset<N> is a fixed-size collection of bits. Both are described as specialized container-like types. They are useful to know about so that you do not mistake them for the variable-size containers above.

Mistakes When Choosing a Container

  • Common mistake

    Assuming an unordered container behaves exactly like an associative container

    What it looks like

    Choosing unordered_map because it looks like map, then expecting the same behavior in every respect

    Why it fails

    The source describes the unordered versions by their lookup mechanism, so you should not expect them to match the associative containers in every respect.

    Do this instead

    Pick the unordered version when fast lookup by key is the main job, and check the behavior you actually depend on.

  • Common mistake

    Treating array<T,N> as a container that grows

    What it looks like

    Adding more elements to an array<T,N> and expecting its size to change

    Why it fails

    Its size is set as part of its type, so it is fixed-size rather than variable-size like vector<T>.

    Do this instead

    Use vector<T> when the number of elements will change.

  • Common mistake

    Using a queue or stack as if it were a general-purpose collection

    What it looks like

    Trying to reach an arbitrary element of a stack<T> by position

    Why it fails

    An adaptor provides a narrower interface suited to a particular access pattern, not the full operations of a general collection.

    Do this instead

    Use the adaptor for its intended access pattern, or choose an ordinary container if you need broader access.

  • Common mistake

    Choosing a keyed container when you only need to keep items in the order they were added, which is a question about position rather than key

Applying the Choice to a Real Need

Worked example

Choosing a container for name-based lookup

A program must store student records and find a record quickly by the student's name. The order in which records were added does not matter, and each name identifies one record.

  1. 1
    Identify how elements are reached

    Records are found by a name, which is a key, so the choice belongs to the keyed families rather than the sequential family.

  2. 2
    Check the main job

    The main job is fast lookup by key, which is the purpose the source singles out for the unordered containers.

  3. 3
    Check the relaxed rules

    Each name identifies one record and each record has its own data, so a map-style container with one value per key fits, rather than a multimap that allows a key to repeat.

  4. 4
    Choose the family

    Since fast lookup by key is the main job, the unordered version of the map is the container the source points to.

Result

Use unordered_map<K,V> with the student name as K and the record as V. Note that this choice follows the source's description of purpose, and the source does not specify the exact record type to use.

EASY

A program must keep a list of tasks in the exact order they were added, and it must sometimes read the task at a given position. Which family should you consider first, and why?

Hints
  • Ask whether you reach elements by position or by key.
  • The sequential family is the one defined by position.

Key Takeaways

  • Standard library containers fall into sequential, associative, and unordered families, and the family tells you whether you reach elements by position or by key.
  • Sequential containers such as vector, list, forward_list, and deque are arranged by position, while associative containers store elements with keys.
  • Unordered containers are optimized for lookup with a key, often a string, and are implemented using hash tables, so they should not be assumed to behave like associative containers in every respect.
  • Adaptors such as queue, stack, and priority_queue offer a narrower interface, while array and bitset are specialized container-like types with fixed size.

Related concepts

More concepts starting with U