12. Joining Collections with $lookup

$lookup performs a left outer join. It attaches matching foreign documents in an array field, even when the data model expects one match. Use $unwind if you need one output document per match; use preserveNullAndEmptyArrays: true if unmatched source records must remain.

Indexes on foreign join fields can matter for performance. _id is indexed by default. Filter source documents early, limit unnecessary foreign fields, and inspect the execution plan for large joins.

Lookup returns an array

Handle match cardinality explicitly instead of assuming the lookup array contains exactly one document.

Code Example
db.orders.aggregate([
  { $match: { status: "delivered" } },
  {
    $lookup: {
      from: "customers",
      localField: "customerId",
      foreignField: "_id",
      as: "customer"
    }
  },
  { $project: { customerId: 1, status: 1, createdAt: 1, customer: 1 } }
]);
Key Takeaways
  • $lookup attaches matching documents in an array.
  • The result is not automatically flattened.
  • Indexes on join fields can be important.
  • Large joins need performance testing.
Knowledge Check

What is the usual type of the field created by $lookup?