Common Mistakes
Overusing $lookup and Poor Schema Design
Joining collections the application should have modelled differently.
Problem. Every request $lookups three collections because the SQL diagram became collections 1:1. Or line items live in order_items so the order page joins.
db.orders.aggregate([
{ $lookup: { from: "order_items", localField: "_id", foreignField: "orderId", as: "items" } },
{ $lookup: { from: "users", localField: "userId", foreignField: "_id", as: "user" } },
{ $lookup: { from: "products", localField: "items.productId", foreignField: "_id", as: "products" } }
]){
userId: ObjectId("64a1b2c3d4e5f6a7b8c90001"),
amount: 4900,
items: [
{ name: "Mechanical Keyboard", quantity: 1, unitPrice: 4500 }
]
}
db.orders.find({ userId }) // history: no lookup
db.users.aggregate([
{ $match: { city: "Pune" } },
{ $lookup: { from: "orders", localField: "_id", foreignField: "userId", as: "orders" } }
]) // report: match first, then join$lookup after $limit of 3 is fine. $lookup on the whole orders collection to decorate a list is a schema smell. Populate in Mongoose is the same join with friendlier syntax — still not free.
Interview question
When is $lookup a sign the schema is wrong?
When the hot path joins collections that are always read together — line items on an order, address on a user. Those should be embedded. $lookup is right for unbounded or independently queried data, after $match/$limit so you do not join the world.
Practice
A justified lookup, and embedded items you unwind instead of joining.