MongoDB $push vs $addToSet: What’s the Difference?

MongoDB has a $push operator and an $addToSet operator, both of which do a very similar thing.

Both operators append values to an existing array. The main difference is in how they deal with arrays that already contain the value you’re trying to append, and also in the modifiers that can be used.

The Differences

Existing ValuesIf the value already exists in the array, $push will still append the value (resulting in duplicate values).
However, $addToSet only appends the value if it doesn’t already exist in the array. Therefore, if the value already exists, $addToSet won’t append it (it will do nothing).
ModifiersThe $push operator can be used with additional modifiers, such as $sort, $slice, and $position, whereas $addToSet can’t (at least, not as of MongoDB 4.4).

Existing Values

Suppose we have a collection with the following documents:

db.players.find()

Result:

{ "_id" : 1, "scores" : [ 1, 5, 3 ] }
{ "_id" : 2, "scores" : [ 8, 17, 18 ] }
{ "_id" : 3, "scores" : [ 3, 5, 5 ] }

Let’s use $addToSet to try to append a value to one of the arrays.

db.players.update(
   { _id: 3 },
   { $addToSet: { scores: 5 } }
)

Output:

WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 0 })

This tells us that there was a matching document (document 3), but it wasn’t modified. It wasn’t modified because the value we tried to insert (5) already exists in the array.

Let’s look in the collection:

db.players.find()

Result:

{ "_id" : 1, "scores" : [ 1, 5, 3 ] }
{ "_id" : 2, "scores" : [ 8, 17, 18 ] }
{ "_id" : 3, "scores" : [ 3, 5, 5 ] }

As expected, document 3 hasn’t been changed.

Let’s try $push instead:

db.players.update(
   { _id: 3 },
   { $push: { scores: 5 } }
)

Output:

WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })

This time we see that the document was modified.

We can verify this by checking the collection again:

db.products.find()

Result:

{ "_id" : 1, "scores" : [ 1, 5, 3 ] }
{ "_id" : 2, "scores" : [ 8, 17, 18 ] }
{ "_id" : 3, "scores" : [ 3, 5, 5, 5 ] }

Modifiers

The $push operator can be used with modifiers such as $position, $sort, and $slice.

The $addToSet operator cannot be used with these modifiers.

Here’s what happens if I try to use these modifiers with $addToSet:

db.players.update(
   { _id: 3 },
   { 
     $addToSet: { 
        scores: {
           $each: [ 12 ],
           $position: 0,
           $sort: 1,
           $slice: 5
        }
      } 
    }
)

Output:

WriteResult({
	"nMatched" : 0,
	"nUpserted" : 0,
	"nModified" : 0,
	"writeError" : {
		"code" : 2,
		"errmsg" : "Found unexpected fields after $each in $addToSet: { $each: [ 12.0 ], $position: 0.0, $sort: 1.0, $slice: 5.0 }"
	}
})

The error message tells us that the $position, $sort, and $slice are unexpected fields (i.e. they shouldn’t be there).

Let’s try the same modifiers with $push:

db.players.update(
   { _id: 3 },
   { 
     $push: { 
        scores: {
           $each: [ 12 ],
           $position: 0,
           $sort: 1,
           $slice: 5
        }
      } 
    }
)

Output:

WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })

This time it worked without error.

Verify the result:

db.players.find()

Result:

{ "_id" : 1, "scores" : [ 1, 5, 3 ] }
{ "_id" : 2, "scores" : [ 8, 17, 18 ] }
{ "_id" : 3, "scores" : [ 3, 5, 5, 5, 12 ] }

We can see that the value has been appended. Even though we specified $position: 0, we also specified $sort: 1, which means that the array was sorted after we positioned it.

We also specified $slice: 5, which limited the array to just 5 elements (which as it turned out, was exactly how many elements were in the array anyway).