LearnAI ToolsCareerPractice BuildsPlayContact
Lesson 3217 min read

$lookup (Joins in MongoDB)

Combine data from two collections in a single aggregation pipeline using $lookup, MongoDB's equivalent of a left outer join.

What $lookup Does

$lookup performs a left outer join, pulling in matching documents from another collection based on a field comparison — MongoDB's answer to a relational JOIN, used specifically within the aggregation framework.

A Basic $lookup

db.orders.aggregate([
{
$lookup: {
from: "users", // the collection to join with
localField: "userId", // field on "orders"
foreignField: "_id", // field on "users"
as: "customer", // name of the new array field holding matches
},
},
]);
Result Shape

Click Run to see what this code prints.

Always Returns an Array

$lookup adds the matched documents as an array (customer, above), even when you expect exactly one match — this reflects that a join could, in general, match multiple documents.

$unwind to Flatten Results

When you know a $lookup will match exactly one document, $unwind flattens that single-element array into a plain embedded object, which is often more convenient to work with.

db.orders.aggregate([
{ $lookup: { from: "users", localField: "userId", foreignField: "_id", as: "customer" } },
{ $unwind: "$customer" }, // customer becomes an object instead of a one-item array
]);

A Pipeline-Based $lookup

A more advanced form of $lookup accepts its own sub-pipeline, letting you filter or reshape the joined data before it's attached — useful for more selective joins.

db.orders.aggregate([
{
$lookup: {
from: "users",
let: { userId: "$userId" },
pipeline: [
{ $match: { $expr: { $eq: ["$_id", "$$userId"] } } },
{ $project: { name: 1, _id: 0 } },
],
as: "customer",
},
},
]);

When to Use $lookup vs Embedding

As covered in the schema design lessons, embedding is often preferred for data that's always read together. $lookup is the right tool specifically when the data was deliberately referenced (not embedded) — because it's large, independently queried, or shared — and you occasionally need to combine it for a specific report or view.

FAQs

It can be more expensive, especially across large collections without good indexes on the join fields — MongoDB's general design philosophy favors embedding over frequent $lookup use for exactly this reason.

It can join collections within the same database only by default; cross-database and cross-cluster scenarios need different tooling (like MongoDB's $merge or application-level joins).

Summary

$lookup enables join-like operations across collections when data has been deliberately referenced rather than embedded. Next, you'll combine everything covered so far into more advanced, multi-stage aggregation pipelines.

Next Lesson →

Advanced Aggregation Pipelines