Querying Arrays with elemMatch

A condition on an array field is tried twice: against the array as a whole, and against every element. { tags: 'scifi' } therefore means "contains" and matches five books, while { tags: [...] } with an array on the right means "equals this exact array, in this exact order". And because each condition is tested against every element independently, two conditions on one array can be satisfied by two different elements. That is the array trap, and it hides in plain sight.

Exact arrays, $all, and two conditions that need one element
db.books.find({ tags: ['scifi', 'classic'] }, { title: 1, _id: 0 })
db.books.find({ tags: ['classic', 'scifi'] }, { title: 1, _id: 0 })
db.books.find({ tags: { $all: ['classic', 'scifi'] } }, { title: 1, _id: 0 })
db.books.find({ 'editions.format': 'ebook', 'editions.pages': { $gt: 315 } }, { title: 1, _id: 0 })
db.books.find({ editions: { $elemMatch: { format: 'ebook', pages: { $gt: 315 } } } },
              { title: 1, _id: 0 })
Output
[ { title: 'Dune' }, { title: 'Kindred' } ]
[]
[ { title: 'Dune' }, { title: 'Kindred' } ]
[ { title: 'The Hobbit' }, { title: 'Project Hail Mary' } ]
[ { title: 'Project Hail Mary' } ]

The reversed literal matches nothing; $all is the order-independent version, and the operator you almost always want. $size takes an exact length ({ tags: { $size: 2 } }, no range form), and a numeric path component addresses a position, so { 'tags.0': 'fantasy' } finds books whose first tag is fantasy.

The Hobbit has no ebook longer than 315 pages: its ebook is 310 pages and its 320-page edition is a hardcover. The fourth query matched it anyway, because one element supplied the format and another the page count. $elemMatch demands a single element satisfy the whole group.

The same document seen by a split filter and by $elemMatch
The same document seen by a split filter and by $elemMatch

The rule holds for scalars too: { ratings: { $gt: 4, $lt: 5 } } matches seven of eight books — any book with a 5 and a 4 — while { ratings: { $elemMatch: { $gt: 4, $lt: 5 } } } returns none. Reach for $elemMatch whenever an array condition has more than one part; a multikey index still serves one of the conditions, and the server re-checks the rest per document (Special Index Types).