Skip to content

Advanced Aggregation

$lookup

A left outer join between collections. Local field, foreign field, and the array you get back.

IntermediateAbout 7 minutes

$lookup is a left outer join. Every input document stays. Matching documents from another collection land in an array on the field you name. No match → that array is [], not a missing document.

db.users.aggregate([
  { $match: { name: "Rahul" } },
  {
    $lookup: {
      from: "orders",
      localField: "_id",
      foreignField: "userId",
      as: "orders"
    }
  }
])
Rahul's orders hung off the user document
{ _id: ObjectId("64a1b2c3d4e5f6a7b8c90001"), name: "Rahul", city: "Bangalore",
  orders: [
    { userId: ObjectId("64a1b2c3d4e5f6a7b8c90001"), amount: 4900, status: "completed", ... },
    { userId: ObjectId("64a1b2c3d4e5f6a7b8c90001"), amount: 1200, status: "pending", ... }
  ]
}
FieldMeaning
fromCollection to join
localFieldField on the current document
foreignFieldField on documents in from
asNew array field. Overwrites if the name already exists

Equality only — and types must match. ObjectId on _id will not join a userId stored as a string. $size on the array is a count, including 0 for users with no orders. $match before $lookup so you join Pune, not the collection. An index on orders.userId (Indexing module) is what stops this from scanning orders once per user.

Interview question

What does $lookup do, and what should you watch out for?

Think about it first.

Practice

Start from users, join orders, count the array.