MongoDB $zip Operator

Beginner
5 min read
Updated: Oct 2026
3 examples
Live preview

What Is $zip?

$zip is a MongoDB aggregation expression that merges two or more arrays by index. Each output item is a small array of the values at that position—like pairing columns into rows.

Remember
$zip = zip() for aggregation

{ $zip: { inputs: [ array1, array2, ... ] } }

["Alice","Bob"] + [92,85]
  → [["Alice",92], ["Bob",85]]

default → stop at shortest length
useLongestLength + defaults → pad shorter arrays

vs $concatArrays → append flat, not pair by index
UsePrefer
Pair arrays by index$zip
Append arrays end-to-end$concatArrays
Transform each zipped row$map after $zip

How to Use $zip in MongoDB

List the arrays to merge, decide shortest vs longest length, then project the pairs.

  1. Choose an expression stage ($addFields or $project).
  2. Pass two or more array expressions in inputs.
  3. Keep the default (shortest length), or set useLongestLength with defaults.
  4. Optionally reshape pairs with $map or $arrayToObject.
PieceRequired?Role
inputsYesArray of arrays to merge by index
useLongestLengthNotrue = pad to longest (needs defaults)
defaultsWith longestOne default per input array for padding

Pseudocode

Pseudocode
for each document:
    arrays = evaluate(inputs)
    if any array is null or missing:
        result = null
    else:
        n = shortestLength(arrays)   // or longest if useLongestLength
        result = []
        for i from 0 to n-1:
            row = [ arrays[0][i], arrays[1][i], ... ]  // pad with defaults if needed
            result.push(row)
    emit { ..., pairs: result }

Cheat Sheet

Keep these facts handy while merging parallel arrays.

TopicDetail
TypeAggregation expression (array operator)
Syntax{ $zip: { inputs: [ … ] } }
Default lengthShortest input array
Pad longeruseLongestLength: true + defaults
Output shapeArray of sub-arrays (one per index)
Nullnull / missing input → null
Common follow-up$map to turn pairs into objects

Live Preview

Watch $zip pair names with scores. Try equal lengths, then a shorter names list.

Simulates $zip the same way as the mongosh examples below.

Result
names  = ["Alice", "Bob", "Carol"]
scores = [92, 85, 78]

$zip → [["Alice", 92], ["Bob", 85], ["Carol", 78]]

Worked Walkthrough — Names + Scores

One class stores names: ["Alice", "Bob", "Carol"] and scores: [92, 85, 78]. Goal: one paired row per student.

Calculation
Document:
  names  = ["Alice", "Bob", "Carol"]
  scores = [92, 85, 78]

index 0 → ["Alice", 92]
index 1 → ["Bob", 85]
index 2 → ["Carol", 78]

$zip result:
  [["Alice", 92], ["Bob", 85], ["Carol", 78]]

MongoDB Examples

We use a small students collection with parallel names and scores arrays. First zip with the default shortest length, then pad with useLongestLength. Run these in mongosh after inserting the sample data once.

Example 1 — Sample Data

Imagine two classes. Math has matching array lengths; Science has an extra score so we can see shortest-length behavior.

mongosh
db.students.insertMany([
  {
    _id: 1,
    className: "Math 101",
    names: ["Alice", "Bob", "Carol"],
    scores: [92, 85, 78]
  },
  {
    _id: 2,
    className: "Science 201",
    names: ["Dave", "Eve"],
    scores: [88, 91, 95]
  }
]);

How the Data Is Stored in MongoDB

After the insert, MongoDB keeps two documents in students. Run find() to see them:

mongosh
db.students.find()

Read each document like this:

Field in MongoDBExamplePlain English
className"Math 101"Class label
names["Alice", "Bob", "Carol"]Student names (parallel column)
scores[92, 85, 78]Scores aligned by the same index

How It Works

  1. Insert once. insertMany adds two class documents to students.
  2. Arrays stay parallel. Index 0 means Alice’s score is 92.
  3. find() shows the same shape. Ready for $zip next.

Example 2 — Basic $zip

Goal: pair each name with its score. Default behavior stops at the shortest array.

mongosh
db.students.aggregate([
  {
    $addFields: {
      pairs: {
        $zip: {
          inputs: ["$names", "$scores"]
        }
      }
    }
  }
]);

How It Works

  1. Read both arrays. Math has three names and three scores—perfect match.
  2. Build one row per index. Index 0 → ["Alice", 92], then Bob, then Carol.
  3. Shortest wins. Science has only two names, so score 95 is dropped under the default setting.

Example 3 — Longest + Defaults

Same Science class, but keep every score by padding the shorter names array with a default.

mongosh
db.students.aggregate([
  { $match: { _id: 2 } },
  {
    $addFields: {
      pairs: {
        $zip: {
          inputs: ["$names", "$scores"],
          useLongestLength: true,
          defaults: ["(unknown)", 0]
        }
      }
    }
  }
]);

How It Works

  1. Filter to Science. $match keeps only the mismatched document.
  2. Use the longest length. scores has 3 items, so output length becomes 3.
  3. Pad with defaults. Missing name at index 2 becomes "(unknown)"; the score 95 is kept.
  4. Easy check. You should see three pairs, ending with ["(unknown)", 95].

Edge Cases & Pitfalls

Check these before using $zip in production pipelines.

length

Default drops extras

Unequal arrays silently truncate to the shortest length. Use useLongestLength when you must keep every item.

defaults

defaults length must match inputs

Provide one default per input array when useLongestLength is true, or the stage can error.

null

Null inputs return null

A missing array field makes the whole $zip result null. Guard with $ifNull if you need [].

vs

Not the same as $concatArrays

$zip builds paired rows. $concatArrays builds one flat list.

Performance Notes

ApproachCostBest for
$zip aloneO(shortest or longest length)Pairing parallel columns
$zip + $mapZip + per-row transformBuilding objects from pairs
Keep arrays aligned upstreamFewer pads / dropsCleaner pipelines

Zipping is cheap for typical classroom-sized arrays. Cost grows with array length per document, not with unrelated collection size after an early $match.

Key Takeaways

  • Role: merge arrays by index into sub-array rows.
  • Default: stop at the shortest input array.
  • Pad: useLongestLength + defaults.
  • Next step: often $map pairs into objects.

One line: $zip answers “what if I lined these arrays up as columns and read each row?”

Frequently Asked Questions

$zip merges two or more arrays by index. Each output element is a sub-array of the values at the same position from every input array—like Python’s zip().
Use { $zip: { inputs: [ array1, array2, ... ], useLongestLength: <boolean>, defaults: <array> } }. Only inputs is required. By default, $zip stops at the shortest array.
By default, extra elements in longer arrays are dropped. Set useLongestLength to true and provide defaults to pad shorter arrays up to the longest length.
$zip pairs by index into sub-arrays. $concatArrays appends arrays end-to-end into one flat list. Use $zip for parallel columns; use $concatArrays for one long list.
If an input expression is null or missing, $zip returns null. It does not throw an error for null input.
Yes. Follow $zip with $map to build { name, score } objects, or use $arrayToObject when one array holds keys and another holds values.

Next: Accumulators

Explore MongoDB accumulator operators used inside $group and related stages.

Accumulators hub →

About the author

Mari Selvan M P
Mari Selvan M P 🔗

Developer, cloud engineer, and technical writer

  • Experience 12 years building web and cloud systems
  • Focus Full Stack Development, AWS, and Developer Education

I write practical tutorials so students and working developers can learn by doing—from databases and APIs to deployment on AWS.

9 people found this page helpful