Choosing Between Sequential and Associative Containers
A useful way to think about any container is that its behavior follows from the data structure underneath it.
Why the Container Choice Matters
When a program has to keep track of many elements, the container you choose decides three things: how quickly you can look an element up, what order you see the elements in when you loop over them, and how the data is laid out internally. These are not small details. Two containers can hold exactly the same data and still behave very differently in your program.
This article focuses on associative containers, the family that includes map and unordered_map, and on the practical guidelines that help you pick one. The guidelines are rules you can apply directly, but the goal is to understand the reasoning behind each rule, so that you can apply it to a requirement the guidelines never mention explicitly. We will trace what happens inside each container when an element is inserted or looked up, then use those traces to make decisions.
A useful way to think about any container is that its behavior follows from the data structure underneath it. Once you know the underlying structure, most of the container's properties follow naturally.
Tracing an Insertion into a Map's Tree
A map is usually implemented as a red-black tree. A tree is made of nodes, and each node holds a key and a value. The arrangement of the nodes is what matters: keys are placed so that moving through the tree in a fixed direction visits the keys in sorted order. This is why a map is the choice when elements must be iterated over in order.
Follow one insertion step by step. Suppose the tree already holds the keys 40, 20, 60, 10 and 30, arranged as shown in the diagram without the key 25. To place 25, you start at the root, 40. Since 25 is less than 40, you move to the left child, 20. Since 25 is greater than 20, you move to the right child, 30. Since 25 is less than 30, the new key lands as the left child of 30. The key is placed by comparing it against nodes on the path down from the root, and the position it lands in is determined by those comparisons.
The tree's shape is what makes ordered iteration possible. Because every key is placed according to its comparison with the keys above it, an in-order walk through the tree (left subtree, then the node, then the right subtree) returns the keys from smallest to largest. You never sort anything during iteration; the sorted order is already present in the arrangement of the nodes.
10 Ann
20 Bo
30 Cy- The keys are inserted in the order 30, 10, 20.
- The loop walks the map in its stored order, not the insertion order.
- The output is sorted because the map keeps its keys arranged in order.
What do you think happens?
Keys 30, 10, and 20 are inserted into a map in that order. In what order does a range-based loop print them?
Reveal answer
Sorted order: 10, 20, 30
A map is usually a red-black tree, and iterating over it in order yields the keys sorted. The order in which the keys were inserted does not determine the iteration order.
Tracing a Lookup in an unordered_map
An unordered_map is usually implemented as a hash table, and it is the choice for fast lookup on large amounts of data. Instead of arranging keys relative to one another, a hash table uses a hash of the key to decide where to look. The steps below trace a lookup for the key apple, as shown in the diagram.
Step one: the key apple is passed to a hash function, which produces a number from the key. Step two: that number is mapped to a bucket index, here 3. Step three: the search happens inside bucket 3, where apple is compared with the keys stored there. The other buckets are never examined for this lookup. This is why the hash table structure supports fast lookup: the work is concentrated in one small place rather than spread across the whole collection.
The consequence for ordering is direct. Keys are placed according to their hashes, not according to any relationship between them, so a hash table does not offer the sorted walk that a tree-based map offers. If your program needs to visit elements in order, the hash table arrangement is the wrong tool, however fast its lookups are.
Ordered Versus Unordered Side by Side
The two containers pair their usual implementations with different strengths. Neither one claims the other's strength, so the choice depends on which property your program needs most. The table below puts the source's points about each container next to each other.
| Property | map | unordered_map |
|---|---|---|
| Usual implementation | Red-black tree | Hash table |
| Typical reason to choose it | Elements must be iterated over in order | Fast lookup on large amounts of data |
| Iteration order | Sorted, as the tree arrangement provides | Not ordered by key; the hash arrangement does not provide sorted order |
| Key requirement from the guidelines | Keys need a natural order (a less-than comparison) | Works for element types that have no natural order |
Ordered and unordered associative containers, as described by the source guidelines
Choosing a Container from Requirements
The source gives three guidelines for this decision. Use unordered containers when you need fast lookup for large amounts of data. Use ordered associative containers such as map and set when you need to iterate over their elements in order. Use unordered containers for element types that have no natural order, meaning types for which no reasonable less-than comparison exists.
Read the flowchart from the top. The ordering question comes first because it is the one hard constraint the source names: if the output must come out in order, an unordered container does not satisfy it, no matter how fast its lookups are. If ordering is not required, the next question is whether the element type has a natural order. If it does not, unordered_map is the choice. If it does, the question becomes whether you need fast lookup on large data, which also points to unordered_map. When neither condition applies, the source offers no default, and you must decide from your other requirements.
Parentheses Versus Braces at Creation
Container creation uses two initializer forms, and the source assigns each one a specific job. The parentheses form is used for sizes. The braces form is used for lists of elements. The same pair of symbols can therefore mean very different things depending on which form you use, and mixing them up is one of the most common sources of surprise with containers.
3 1 3sized(3)uses the parentheses form, which gives a size: three elements.- listed{3} uses the braces form, which gives a list: one element whose value is 3.
- Both declarations use the same number, but they create different containers.
What do you think happens?
After the declaration std::vector<int> listed{3};, how many elements does listed hold?
Reveal answer
One element holding 3
The braces form is for lists of elements, so {3} is a list containing the single value 3. The parentheses form, sized(3), is the one that would create three elements.
A practical habit follows from this split. When you want a container with a given number of elements, write the count in parentheses. When you want a container that starts with specific values, write those values inside braces. Reading the symbols as a statement of intent, size or list, makes the meaning of each declaration clear before you run the program.
Passing Containers and Returning Them
Once a container exists, it often moves between functions. The source gives a direct guideline for this: pass a container by reference, and return a container by value. Passing by reference means the function receives the caller's container itself rather than a separate parameter, so the function works on the same container the caller holds. Returning by value means the function hands the container back as its result, and the caller receives it as the function's output.
2make_scoresreturns its map by value, and roster receives that result.add_scoretakes the map by reference, so it modifies roster itself rather than a copy.- After
add_scoreruns, roster holds two entries, Ann and Bo.
Read the two directions separately. Inputs travel into a function by reference, so the function changes the caller's container. Results travel back out by value, so the caller ends up holding the container the function produced. In the example, add_score adds Bo to the same roster that main owns, and make_scores hands its new map back to main as its result.
The source also gives a general preference for data structures: prefer compact and contiguous ones. It warns that a list is relatively expensive to traverse. These two points tell you what to avoid when a container will be walked through repeatedly. A structure whose elements are scattered and costly to step through is a weaker default than one whose elements sit close together. These preferences sit alongside the ordered and unordered rules rather than replacing them. When you choose a structure, ask whether it is compact and contiguous, and whether you will traverse it often enough for traversal cost to matter.
Mistakes Beginners Make
Common mistake Assuming an unordered_map will print its keys in sorted order
What it looks likeInserting 30, 10, 20 into an unordered_map and expecting the loop to print 10, 20, 30.
Why it failsAn
unordered_mapis usually a hash table, and its arrangement follows hashes rather than any relationship between keys, so it does not provide the sorted walk a map provides.Do this insteadWhen the output must be in order, choose map or set. The ordering requirement comes first in the decision flowchart.
Common mistake Choosing map for an element type that has no natural order
What it looks likeUsing a map keyed by a custom type that defines no less-than comparison.
Why it failsOrdered associative containers rely on a natural ordering of their keys, and the source says unordered containers are the guideline for element types with no natural order.
Do this insteadUse unordered_map for such types, provided the element type can be used as a key in the unordered container.
Common mistake Writing parentheses when a list of values was intended
What it looks likestd::vector<int> v(10); expecting a vector holding the single value 10.
Why it failsParentheses give a size. The parentheses form creates ten elements, not a list containing one element whose value is 10.
Do this insteadUse braces, as in std::vector<int> v{10}, for a list of elements, and parentheses only when you want that number of elements.
Common mistake Passing a container by value into a function that should modify it
What it looks likeA function declared as taking a map by value, then adding an entry to its parameter and expecting the caller's map to change.
Why it failsPassing by value gives the function its own separate container, so changes made inside the function do not reach the caller's container. The source guideline is to pass containers by reference.
Do this insteadTake the container by reference, as in std::map<std::string, int>& scores, when the function must work on the caller's container.
Applying the Choice to Two Requirements
A program stores player records keyed by player name. Requirement A: it must print every name in alphabetical order when a report is generated. Requirement B: a separate lookup table stores a very large number of records keyed by a numeric ID, and it must answer lookups quickly with no ordered output needed. For each requirement, choose map or unordered_map, and name the guideline from the source that justifies your choice.
Hints
- Start with the ordering question. Does Requirement A need sorted output?
- For Requirement B, identify the two properties the source ties to
unordered_map: fast lookup on large data. - Check the flowchart order: sorted iteration first, then the element-type question, then fast lookup.
Key Takeaways
- A map is usually implemented as a red-black tree, so an in-order walk visits its keys in sorted order; choose it when elements must be iterated over in order.
- An
unordered_mapis usually a hash table, which uses a hash of the key to find its bucket; choose it for fast lookup on large amounts of data or for element types with no natural order. - Check the ordering requirement first, because an unordered container cannot provide sorted output; the source gives no default beyond its three guidelines.
- Use parentheses for sizes and braces for lists of elements, since the same symbols mean different things in each form.
- Pass containers by reference and return them by value, and prefer compact, contiguous structures because lists are relatively expensive to traverse.