배열 업데이트 연산자: $push, $pull, $addToSet
대상 배열 업데이트 연산자를 사용하여 문서 내부의 배열에 항목을 추가하거나 제거합니다.
배열 업데이트 연산자: $push, $pull, $addToSet은(는) CoddyKit의 무료 MongoDB Academy 강의입니다. 이것은 4개 중 3번째 강의입니다. 아래에서 전체 강의를 무료로 읽을 수 있으며, 내장 코드 에디터와 24/7 AI 튜터와 함께 브라우저에서 직접 실습할 수 있습니다. 이 강의는 MongoDB Academy 학습 경로의 일부이며, 진행 상황이 웹과 CoddyKit 앱에 동기화됩니다. MongoDB Academy 강의에는 총 4개의 강의가 포함되어 있습니다.
이 강의의 일부는 아직 번역되지 않았으며 영어로 표시됩니다.
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.
자주 묻는 질문
“배열 업데이트 연산자: $push, $pull, $addToSet” 강의는 무료인가요?
네 — “배열 업데이트 연산자: $push, $pull, $addToSet” 전체 내용을 이 웹사이트에서 무료로 읽을 수 있습니다. 인터랙티브하게 실습하려면(내장 코드 에디터와 24/7 AI 튜터), CoddyKit PRO로 업그레이드하면 MongoDB Academy 강의 전체를 잠금 해제할 수 있습니다. MongoDB Academy 강의에는 총 4개의 강의가 포함되어 있습니다.
“배열 업데이트 연산자: $push, $pull, $addToSet”에서 뭘 배우나요?
대상 배열 업데이트 연산자를 사용하여 문서 내부의 배열에 항목을 추가하거나 제거합니다. 브라우저에서 직접 실행하는 실습 코드로 MongoDB Academy을(를) 배우며, 24/7 AI 튜터가 강의를 진행하면서 질문에 답변해줍니다.
MongoDB Academy을(를) 시작하는 데 경험이 필요한가요?
사전 경험은 필요하지 않습니다. CoddyKit의 MongoDB Academy은(는) 초급자부터 고급 학습자까지를 위해 구성되어 있으므로, 여기서 시작하거나 처음부터 시작할 수 있으며 자신의 속도대로 진행할 수 있습니다. 이것은 4개 중 3번째 강의입니다.
“배열 업데이트 연산자: $push, $pull, $addToSet” 강의는 얼마나 걸리나요?
대부분의 CoddyKit 강의는 약 5~10분이 소요됩니다. 각 강의는 간결하고 인터랙티브하여 꾸준한 진행이 가능하며, 웹과 앱에서 중단한 부분부터 바로 시작할 수 있습니다.
이 MongoDB Academy 강의에서 코드를 작성하고 실행할 수 있나요?
네. 모든 MongoDB Academy 강의에는 내장 코드 에디터가 포함되어 있으므로, 브라우저에서 바로 실제 코드를 작성하고 실행한 후 즉시 AI 피드백을 받을 수 있습니다 — 로컬 설정이 필요 없습니다.
이 강의의 모든 강의
- $set 및 $unset을 사용한 updateOne과 updateMany
- 증가, 곱셈, 최솟값/최댓값 연산자
- 배열 업데이트 연산자: $push, $pull, $addToSet
- deleteOne과 deleteMany로 안전하게 문서 삭제하기