Array Update Operators: $push, $pull, $addToSet
Learners will add and remove items from arrays inside documents using targeted array update operators.
Array Update Operators: $push, $pull, $addToSet is a free MongoDB Academy lesson on CoddyKit — lesson 3 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the MongoDB Academy learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.
Arrays as First-Class Citizens
Arrays inside MongoDB documents are common—a product has tags, a user has roles, an order has items. When these arrays need to change (add a tag, remove a role, append an order item), you need array update operators that modify the array in-place rather than replacing the entire document.
MongoDB provides a full set of operators for this purpose: $push adds elements, $pull removes elements by value, $pop removes from ends, and $addToSet adds only if unique. All of these are atomic—the change happens in a single server-side operation.
$push: Adding Elements to an Array
The $push operator appends one or more elements to an array field. If the field does not exist, $push creates it as a new array containing the pushed element. If the field exists but is not an array, the operation fails.
A single $push with a plain value adds one element. To add multiple elements in one operation, combine $push with the $each modifier. $push does not check for duplicates—use $addToSet if you need uniqueness.
// Push a single element
db.posts.updateOne(
{ _id: postId },
{ $push: { tags: 'mongodb' } }
);
// tags: ['node', 'backend'] -> ['node', 'backend', 'mongodb']
// Push multiple elements using $each
db.posts.updateOne(
{ _id: postId },
{ $push: { tags: { $each: ['databases', 'nosql'] } } }
);
// tags: ['node'] -> ['node', 'databases', 'nosql']
// Push a sub-document
db.users.updateOne(
{ _id: userId },
{ $push: { addresses: { street: '123 Main St', city: 'Chicago' } } }
);$push With $sort and $slice
Combining $push with the $sort and $slice modifiers creates a capped sorted array: push new elements, sort the array, then trim it to a maximum length. This pattern is perfect for maintaining a 'top N' list or a 'recent N items' log.
The $slice modifier limits the array to the first N elements after sorting. Use negative values for last N (e.g., $slice: -5 keeps the last 5 after sort). This combination requires $each even if you are pushing a single item.
// Maintain a 'top 5 scores' list
db.players.updateOne(
{ _id: playerId },
{
$push: {
topScores: {
$each: [{ score: 9800, date: new Date() }],
$sort: { score: -1 }, // Sort by score descending
$slice: 5 // Keep only top 5
}
}
}
);
// After: topScores always has max 5 entries, highest scores first$pull: Removing Elements by Value
The $pull operator removes all elements from an array that match a specified value or condition. Unlike $pop (which removes from an end), $pull removes elements by their value anywhere in the array.
You can pass a simple value (for primitive arrays) or a query condition (for arrays of sub-documents). $pull removes all matching elements in one atomic operation—there is no need to know the position of the element.
// Remove a tag by value
db.posts.updateOne(
{ _id: postId },
{ $pull: { tags: 'outdated' } }
);
// tags: ['mongodb', 'outdated', 'nosql'] -> ['mongodb', 'nosql']
// Removes ALL occurrences of 'outdated'
// Pull with a condition on array of sub-documents
db.orders.updateOne(
{ _id: orderId },
{ $pull: { items: { sku: 'REMOVED-ITEM' } } }
);
// Removes all items where sku = 'REMOVED-ITEM' from the items array$pop: Removing From Array Ends
The $pop operator removes the first or last element of an array without needing to know the value. Use $pop: { field: 1 } to remove the last element, and $pop: { field: -1 } to remove the first element.
$pop is useful for implementing queues and deques: push to the back with $push, pop from the front with $pop: -1. However, for production queue systems, consider dedicated solutions—document-based queues have limitations under high concurrency.
// Remove the last element
db.playlists.updateOne(
{ _id: playlistId },
{ $pop: { songs: 1 } } // 1 = last
);
// songs: ['A', 'B', 'C'] -> ['A', 'B']
// Remove the first element
db.queues.updateOne(
{ _id: queueId },
{ $pop: { items: -1 } } // -1 = first
);
// items: ['task1', 'task2', 'task3'] -> ['task2', 'task3']$addToSet: Unique Element Insertion
The $addToSet operator adds an element to an array only if it is not already present. If the element already exists, the operation is a no-op—no duplicate is added. This is ideal for maintaining sets of unique values like tags, user roles, or category memberships.
Like $push, $addToSet supports the $each modifier to add multiple unique elements in one operation. If the field does not exist, it creates the array. BSON type and value must match exactly for the duplicate check.
// Add tag only if not already present
db.posts.updateOne(
{ _id: postId },
{ $addToSet: { tags: 'mongodb' } }
);
// If tags already has 'mongodb': no change
// If not: 'mongodb' is added
// Add multiple unique roles with $each
db.users.updateOne(
{ _id: userId },
{ $addToSet: { roles: { $each: ['viewer', 'commenter'] } } }
);
// Only adds roles that aren't already in the array
// 'viewer' already exists? Skipped. 'commenter' new? Added.$push vs $addToSet: Choosing
The choice between $push and $addToSet depends on whether order and duplicates are acceptable:
- Use $push when: order matters, duplicates are acceptable (or expected), you need sorted arrays with
$sort/$slice, or you are building a log/history where repeated values make sense - Use $addToSet when: you are managing a set of unique values (roles, permissions, tags, subscriptions) and adding an existing value should be a no-op
Keep in mind that $addToSet must scan the existing array to check for duplicates—for very large arrays (thousands of elements), this can be slow. Consider a separate collection for large unique sets.
// $push for ordered history (duplicates ok)
db.users.updateOne(
{ _id: userId },
{ $push: { loginHistory: { at: new Date(), ip: req.ip } } }
);
// Same IP can appear multiple times - that's fine for history
// $addToSet for unique roles (no duplicates)
db.users.updateOne(
{ _id: userId },
{ $addToSet: { roles: 'editor' } }
);
// 'editor' is either added or already there - always uniqueRemoving Specific Elements From Nested Arrays
When your document has an array of sub-documents and you need to remove a specific sub-document by one of its fields, use $pull with a condition expression. The condition is evaluated against each array element and all matching elements are removed.
For removing multiple specific values at once from a primitive array, $pullAll is a shortcut that accepts an array of values to remove: { $pullAll: { tags: ['oldTag1', 'oldTag2'] } }. It is equivalent to { $pull: { tags: { $in: ['oldTag1', 'oldTag2'] } } }.
// Remove all items in an order for a discontinued product
db.carts.updateOne(
{ _id: cartId },
{
$pull: {
items: { productId: ObjectId('discontinued-id') }
}
}
);
// $pullAll: remove multiple values at once
db.posts.updateOne(
{ _id: postId },
{ $pullAll: { tags: ['spam', 'outdated', 'test'] } }
);
// Removes all occurrences of 'spam', 'outdated', and 'test'Array Length Management
Be cautious about unbounded array growth. Arrays that grow without limit (unbounded arrays) cause documents to grow beyond MongoDB's 16 MB limit and degrade performance as the array index size grows.
Strategies for bounded arrays:
- Use
$push+$slice: -Nto keep only the last N elements (rolling window) - Set a business rule: maximum 50 items in cart, maximum 100 history entries
- For truly unbounded collections, move to a separate collection with a reference instead of embedding
// Keep only the last 100 login history entries
db.users.updateOne(
{ _id: userId },
{
$push: {
loginHistory: {
$each: [{ at: new Date(), ip: req.ip }],
$slice: -100 // Keep last 100 (most recent)
}
}
}
);
// loginHistory never exceeds 100 entries
// Oldest entries are automatically droppedReal-World: User Following System
Social features like 'follow user' and 'unfollow user' map naturally to $addToSet and $pull. When a user follows someone, add the followed user's ID to the follower's following array. When they unfollow, pull it out. The atomic nature of these operators means two simultaneous follow/unfollow actions on the same document will be serialized correctly.
For large social graphs (millions of followers), this embedded array approach hits the 16 MB document limit. At scale, move relationships to a dedicated follows collection with followerId and followeeId fields.
// Follow a user
async function followUser(followerId, followeeId) {
await db.collection('users').updateOne(
{ _id: new ObjectId(followerId) },
{ $addToSet: { following: new ObjectId(followeeId) } }
);
// Optionally: add to followee's followers array too
}
// Unfollow a user
async function unfollowUser(followerId, followeeId) {
await db.collection('users').updateOne(
{ _id: new ObjectId(followerId) },
{ $pull: { following: new ObjectId(followeeId) } }
);
}The Positional $ Operator in Updates
When you need to update a specific element within an array that matched your query, use the positional $ operator. In the update document, $ refers to the first array element that matched the filter condition.
For example, to mark a specific order item as shipped, filter by { 'items.sku': 'A1' } and then update { : { 'items.$.shipped': true } }. The $ acts as a placeholder for the index of the matched array element. If multiple elements match, only the first is updated.
// Update a specific array element using positional operator
// Find order containing item sku 'A1', mark it shipped
db.orders.updateOne(
{ _id: orderId, 'items.sku': 'A1' }, // Filter: includes array condition
{ : { 'items.$.shipped': true } } // $: matched array element index
);
// Before: items: [{sku:'A1',shipped:false},{sku:'B2',shipped:false}]
// After: items: [{sku:'A1',shipped:true},{sku:'B2',shipped:false}]Quick Check
Test your understanding of MongoDB & NoSQL Databases concepts from this lesson.
Lesson Recap
In this lesson you learned: $push appends elements to an array and supports $each/$sort/$slice modifiers for maintaining capped sorted arrays like top-N lists, $pull removes all matching elements by value or condition—use $pullAll to remove multiple specific values at once, and $addToSet adds an element only if absent—ideal for maintaining unique sets like user roles, tags, and permissions without application-level duplicate checks. Next up we explore how to safely delete documents with deleteOne and deleteMany.
Frequently asked questions
Is the “Array Update Operators: $push, $pull, $addToSet” lesson free?
Yes — the full text of “Array Update Operators: $push, $pull, $addToSet” is free to read here on the web, and the MongoDB Academy course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the MongoDB Academy course, upgrade to CoddyKit PRO.
What will I learn in “Array Update Operators: $push, $pull, $addToSet”?
Learners will add and remove items from arrays inside documents using targeted array update operators. You practise MongoDB Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.
Do I need any experience to start MongoDB Academy?
No prior experience is required. MongoDB Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 3 of 4, so you can start here or from the beginning and move at your own pace.
How long does the “Array Update Operators: $push, $pull, $addToSet” lesson take?
Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.
Can I write and run code in this MongoDB Academy lesson?
Yes. Every MongoDB Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.
All lessons in this course
- updateOne and updateMany With $set and $unset
- Increment, Multiply, and Min/Max Operators
- Array Update Operators: $push, $pull, $addToSet
- Deleting Documents Safely With deleteOne and deleteMany