· via dev.to (home feed)
MongoDB 8.3 adds arrayIndexAs to $map, cutting index workaround overhead by up to 31%
MongoDB 8.3 lets $map expose element positions directly via arrayIndexAs. dev.to benchmarks show the $range/$arrayElemAt workaround it replaces costs 15-31% more on large arrays.

What 8.3 changes
MongoDB 8.3 introduces arrayIndexAs, an optional parameter on the aggregation framework's $map operator that exposes the position of each element as the pipeline iterates. According to a dev.to post that benchmarked the change, this closes a long-standing gap: $map, $filter and $reduce have never told you which index you were processing, so pipelines that needed a value paired with its position had to construct that pairing themselves.
The established workaround mapped over a $range spanning the array's $size, then used $arrayElemAt to pull each value back out by position — two steps to rebuild information the loop already had. With arrayIndexAs, $map takes the array directly and binds the current index to a named variable. The dev.to author verified that both forms produce identical output before trusting any timings.
What the benchmark found
The tests ran MongoDB 8.3.11 in Docker against single documents holding arrays of 10,000 to 320,000 floats, timed through explain({executionStats}) — the server's own execution time, with no network or driver overhead — using the minimum of five runs per size to sidestep WiredTiger cache warm-up.
At 320,000 elements the old pattern took 134ms against 102ms with arrayIndexAs, a 1.31x advantage and the source of the headline figure. The rest of the run: 3ms versus 2ms at 10,000 elements, 14ms versus 12ms at 40,000, 29ms versus 24ms at 80,000, and 58ms versus 50ms at 160,000. The post reports a steady 15–30% gap once arrays reach the tens of thousands of elements, and cautions that below roughly 10,000 the difference sits inside run-to-run noise.
The author expected worse. If $arrayElemAt scanned from the front of the array on every call, cost would grow quadratically — but MongoDB's in-memory array representation supports direct positional access, so the penalty is closer to a fixed per-element overhead from building the range and re-fetching a value the loop already held.
The gap narrows under load
A single quiet connection is not a production instance. Rerunning ten aggregations against the 320,000-element document via pymongo, averaged over four runs, the old pattern took 3,137ms versus 2,788ms when run sequentially — about a 13% gap. With all ten running concurrently on a four-core container, that fell to 1,950ms versus 1,859ms, roughly 5%. Once queries are queuing for cores, shaving an aggregation's inner loop buys less. The actionable conclusion, per the author: arrayIndexAs still helps under concurrency, but on a CPU-saturated instance core count and query volume are the bigger levers.
The post also records a measurement mistake worth flagging: an early harness that spawned ten separate mongosh processes and timed them from the shell produced results inverted from every other run, because process and connection start-up dwarfed the queries themselves. A single persistent Python client eliminated the noise.
An upgrade trap in the error messages
MongoDB 8.3 also ships $createObjectId, which generates a random ObjectId inside a pipeline and accepts exactly one argument, an empty object; converting an existing value requires $toObjectId. Separately, the as variable and arrayIndexAs may not share a name.
The sharper catch involves upgrades. On a cluster whose binaries are at 8.3 but whose featureCompatibilityVersion is still 8.2, $createObjectId fails with an explicit compatibility error pointing to the documentation, while arrayIndexAs fails with a generic Unrecognized parameter to $map message that reads like a typo, with no mention of compatibility at all. Running setFeatureCompatibilityVersion to 8.3 fixes both immediately — the gap, the author argues, is in the error message, not the remedy.
An index variable that already exists
MongoDB's documentation states that $$IDX holds the current index inside $map even when arrayIndexAs is not declared, and the author verified this directly: projecting $$IDX alone returned 0 through n−1. That makes the new parameter largely a readability feature for giving the index a custom name, rather than the only route to positional information.
Why it matters
The change itself is small — an optional field on one aggregation operator — but it retires a construction that has likely been copied through thousands of pipelines, and the measurements show that construction costs something real: a consistent 15–31% overhead on arrays large enough for it to matter. Equally useful is the framing around the numbers. Single-connection benchmarks overstate the benefit under concurrent load, the anticipated quadratic cost turned out to be linear, and a misleading error message — not the feature — is the likeliest source of lost time for teams upgrading to 8.3. It is a good reminder that ergonomic wins in query languages deserve measurement, and that the measurement deserves scrutiny.
- #mongodb
- #aggregation
- #databases
- #benchmarks
- #nosql