docs: document the complexity of ordered_map operations (#5353)

* docs: document the complexity of ordered_map operations

ordered_map stores its elements in a std::vector in insertion order and
has no lookup index, so emplace, operator[], at, find, count, erase, and
insert are all linear scans. The documentation stated no complexity for
any operation, neither in ordered_map.md nor in ordered_json.md.

Add a per-operation complexity table and note the consequence: building
or parsing an ordered_json object of n keys is O(n^2). Measured with
-O2 -DNDEBUG for parsing a flat object of n keys, ordered_json is 5x
slower than json at n=2000 and 54x slower at n=16000, with the timings
quadrupling per doubling of n. Cross-reference the table from
ordered_json.md and from the object order page, which recommends
ordered_json without mentioning the cost.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* docs: move the Complexity section after Member functions

scripts/check_structure.py enforces a fixed section order for pages under
docs/mkdocs/docs/api, in which Complexity comes after Member functions.
The section had been placed right after Iterator invalidation, which made
ci_test_build_documentation fail with structure/section_order.

No content change beyond the move; the table columns are realigned to the
narrower content.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

---------

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
This commit is contained in:
Niels Lohmann
2026-08-04 08:45:35 +02:00
committed by GitHub
parent 173f2a7407
commit c2e1cc50e0
3 changed files with 54 additions and 0 deletions
+6
View File
@@ -13,6 +13,12 @@ Therefore, adding object elements can yield a reallocation in which case all ite
[`end()`](basic_json/end.md) iterator) and all references to the elements are invalidated. Also, any iterator or
reference after the insertion point will point to the same index, which is now a different value.
## Complexity
[`ordered_map`](ordered_map.md) has no lookup index: every key-based object operation is a linear scan, so building or
parsing an object of `n` keys costs O(n²) rather than O(n log n). See
[`ordered_map` complexity](ordered_map.md#complexity) for the per-operation table and for measured numbers.
## Examples
??? example
+42
View File
@@ -56,6 +56,48 @@ std::equal_to<> // since C++14
- **find**
- **insert**
## Complexity
Because the elements are stored in a `std::vector` in insertion order, there is no index to look a key up by. Every
key-based operation performs a **linear scan** over the stored elements. With `n` denoting the number of elements in the
container:
| Operation | Complexity | Note |
|----------------------------------------|----------------|----------------------------------------------------------|
| **emplace** | O(n) | scans for an existing key, then appends (amortized O(1)) |
| **operator\[\]** | O(n) | delegates to **emplace** (non-const) or **at** (const) |
| **at** | O(n) | throws `#!cpp std::out_of_range` if the key is not found |
| **find** | O(n) | |
| **count** | O(n) | the result is always 0 or 1 |
| **erase(key)** | O(n) | scan, then move the remaining elements one position down |
| **erase(pos)**, **erase(first, last)** | O(n) | moves all elements after the erased range |
| **insert(value)** | O(n) | equivalent to **emplace** |
| **insert(first, last)** | O((n + m) * m) | for `m` inserted elements |
This differs from `#!cpp std::map`, where the same operations are O(log n).
!!! warning "Quadratic cost of building large objects"
Because every insertion scans all elements inserted so far, building an object of `n` distinct keys costs
**O(n²)** in total. This applies to filling an [`ordered_json`](ordered_json.md) object key by key as well as to
parsing one, since the parser inserts each key as it is read.
The cost is negligible for the object sizes typically found in configuration files or API payloads, but it grows
steeply for machine-generated objects with many thousands of keys. Measured with `-O2 -DNDEBUG` for parsing a flat
object of `n` keys, relative to `#!cpp nlohmann::json` (which uses `#!cpp std::map`):
| `n` | `json` | `ordered_json` | factor |
|--------|--------|----------------|--------|
| 2000 | 0.7 ms | 3.6 ms | 5× |
| 4000 | 0.8 ms | 14.0 ms | 19× |
| 8000 | 1.6 ms | 67.8 ms | 43× |
| 16 000 | 3.3 ms | 181.6 ms | 54× |
If key order matters for objects of that size, consider a container with a lookup index, such as
[`tsl::ordered_map`](https://github.com/Tessil/ordered-map)
([integration](https://github.com/nlohmann/json/issues/546#issuecomment-304447518)), as the object type -- see
[object order](../features/object_order.md).
## Examples
??? example
@@ -53,6 +53,12 @@ If you do want to preserve the **insertion order**, you can use the type [`nlohm
Alternatively, you can use a more sophisticated ordered map like [`tsl::ordered_map`](https://github.com/Tessil/ordered-map) ([integration](https://github.com/nlohmann/json/issues/546#issuecomment-304447518)) or [`nlohmann::fifo_map`](https://github.com/nlohmann/fifo_map) ([integration](https://github.com/nlohmann/json/issues/485#issuecomment-333652309)).
The [`ordered_map`](../api/ordered_map.md) behind `nlohmann::ordered_json` is deliberately minimal and has no lookup
index, so every key access is a linear scan and building an object of `n` keys costs O(n²). This is unnoticeable at
typical object sizes but becomes significant for objects with many thousands of keys; see
[`ordered_map` complexity](../api/ordered_map.md#complexity). The alternatives above keep a lookup index and do not
have this cost.
### Notes on parsing
Note that you also need to call the right [`parse`](../api/basic_json/parse.md) function when reading from a file.