Arrays get their own operators so you never read a document, splice a JavaScript array and write it back — a read-modify-write that discards any element another client added in between. $push appends, $addToSet appends only if the value is absent, $pull and $pullAll remove by condition or list, $pop removes an end element.
db.reviews.insertOne({ _id: 1, title: "Refactoring",
tags: ["design"], scores: [{ user: "ann", stars: 2 }, { user: "bo", stars: 5 }] });
db.reviews.updateOne({ _id: 1 }, { $push: { tags: { $each: ["java", "oop"] } } });
db.reviews.updateOne({ _id: 1 }, { $addToSet: { tags: "java" } });
db.reviews.updateOne({ _id: 1 }, { $pull: { tags: "oop" } });
db.reviews.findOne({ _id: 1 }).tags;[ 'design', 'java' ]
$push without $each appends its argument as one element, so pushing an array nests an array. The $addToSet call is a no-op because "java" is already present; it compares whole values, so two subdocuments are equal only if their fields match in the same order.
Updating an element inside an array needs a way to name it: the positional $ is the first element the query filter matched, so the filter must mention the array; $[<id>] with arrayFilters targets every element matching a condition; bare $[] targets all of them.
db.reviews.updateOne({ _id: 1, "scores.user": "ann" }, { $set: { "scores.$.stars": 3 } });
db.reviews.updateOne({ _id: 1 }, { $inc: { "scores.$[low].stars": 1 } },
{ arrayFilters: [{ "low.stars": { $lt: 5 } }] });
console.log(db.reviews.findOne({ _id: 1 }).scores);
db.reviews.updateOne({ _id: 1 },
{ $push: { scores: { $each: [{ user: "cy", stars: 2 }],
$sort: { stars: -1 }, $slice: 2 } } });
console.log(db.reviews.findOne({ _id: 1 }).scores);[ { user: 'ann', stars: 4 }, { user: 'bo', stars: 5 } ]
[ { user: 'bo', stars: 5 }, { user: 'ann', stars: 4 } ]Ann moves from 2 to 3 by name, then the filtered positional update adds one to every score under five, which catches Ann and leaves Bo alone. Drop the "scores.user": "ann" clause and the $ has nothing to bind to: the server answers "The positional operator did not find the match needed from the query." In the last write Cy's review is appended, the array is re-sorted by stars descending, and $slice: 2 keeps the top two, dropping Cy in the same round trip — the idiomatic way to bound a "top 3 scores" list. One last trap: $pull takes a condition when elements are documents, so { $pull: { scores: { user: "cy" } } } removes every element whose user is cy, while with scalars it matches the value.