Matching and presentation are separate stages. A query determines the full candidate set; sort, from, size and _source determine what the client receives. A correct filter can still yield an incorrect exercise answer if the page, order or returned fields differ from the contract.
Relevance scores depend on query structure and corpus statistics, including term frequency and document length. They are neither probabilities nor permanent constants. Compare relative document order when reasoning about relevance. Explicit field sorting can replace relevance ordering, so use it only when the requirement asks for it.
- Query candidates
- Eligibility filters
- Score or field sort
- from + size
- _source projection
Filtering happens before pagination, and source projection changes the returned payload rather than document eligibility.
Examples use the public products dataset on Elasticsearch 9.4.3. Responses are shortened JSON fragments: timing, shard and index metadata are omitted. Numeric scores are illustrative and can vary. Examples teach the method; exercise values may differ.
Sort by price
sort is an ordered list of keys. Elasticsearch compares the first key, then the second only when the first is equal. The example asks for the most expensive products, with id ascending to resolve equal prices. Ascending and descending orders are expressed as asc and desc.
Sort on fields with sortable indexed values, such as price, rating, id and brand.keyword. The analyzed name field is not a suitable keyword sort key in this mapping. When explicit non-score sorting is used, _score may be null because scoring is not needed; null is not evidence of a failed query.
Try a different scenario
{
"query": {
"match_all": {}
},
"size": 2,
"_source": [
"name",
"price"
],
"sort": [
{
"price": "desc"
},
{
"id": "asc"
}
]
}Elasticsearch response
{
"hits": {
"total": {
"value": 300,
"relation": "eq"
},
"hits": [
{
"_id": "10",
"_score": null,
"_source": {
"name": "Logitech Wireless Charger 10",
"price": 2499
}
},
{
"_id": "20",
"_score": null,
"_source": {
"name": "Samsung Wireless Charger 20",
"price": 2499
}
}
]
}
}How to read this result
Prices descend. If several products have the highest price, the lower id comes first. Compare sort priority rather than assuming the second key reorders the whole result.
Fetch the second page
from is a zero-based offset, not a page number. For page p with length n, use from = (p − 1) × n and size = n. The example requests page four of three items, so from is 9. Filtering and sorting occur before the offset is applied.
Use the same deterministic sort for every page. Otherwise equal values can produce overlaps or omissions between pages. This trainer caps returned hits at 100 and from + size at 500; it is designed for small educational pages rather than deep production pagination.
Try a different scenario
{
"query": {
"match_all": {}
},
"size": 3,
"_source": [
"name",
"price"
],
"sort": [
{
"id": "asc"
}
],
"from": 9
}Elasticsearch response
{
"hits": {
"total": {
"value": 300,
"relation": "eq"
},
"hits": [
{
"_id": "10",
"_score": null,
"_source": {
"name": "Logitech Wireless Charger 10",
"price": 2499
}
},
{
"_id": "11",
"_score": null,
"_source": {
"name": "Dell Wireless Headphones 11",
"price": 49
}
},
{
"_id": "12",
"_score": null,
"_source": {
"name": "apple Premium Wireless Headphones 12",
"price": 99
}
}
]
}
}How to read this result
The IDs are 10, 11 and 12, while total still describes all 300 matches. Page length and population size answer different questions.
Choose returned fields
_source filtering selects original JSON properties returned for each hit. It does not change the query, its score or the fields available for sorting. The example returns only name and category while sorting by id, demonstrating that a sort field need not be returned.
_id and _score are hit metadata, outside _source. Requesting fewer source properties therefore does not remove the hit identifier. Use original field paths rather than assuming that mapping multi-fields such as brand.keyword were separately written into _source.
Try a different scenario
{
"query": {
"match_all": {}
},
"size": 2,
"_source": [
"name",
"category"
],
"sort": [
{
"id": "asc"
}
]
}Elasticsearch response
{
"hits": {
"total": {
"value": 300,
"relation": "eq"
},
"hits": [
{
"_id": "1",
"_score": null,
"_source": {
"name": "Wireless",
"category": "Smartphones"
}
},
{
"_id": "2",
"_score": null,
"_source": {
"name": "Reference device",
"category": "Audio"
}
}
]
}
}How to read this result
Each source object has exactly name and category. The hit still has _id and _score, and the order still follows id.
Prioritize title matches
In multi_match, the caret notation name^3 gives the name field extra weight relative to an unboosted description. The example searches travel in both fields. Boost changes the contribution of a field; it does not guarantee that every title match universally outranks every description match regardless of content and corpus statistics.
Do not multiply a displayed _score yourself or compare it to a hardcoded target. The engine computes the score from the query and index. In the trainer, a relevance exercise compares document-order relationships and the requested construction. Its task may require a particular boost value even when another value looks plausible.
Try a different scenario
{
"query": {
"multi_match": {
"query": "travel",
"fields": [
"name^3",
"description"
]
}
},
"size": 2,
"_source": [
"name",
"description"
]
}Elasticsearch response
{
"hits": {
"total": {
"value": 178,
"relation": "eq"
},
"hits": [
{
"_id": "3",
"_score": 4.8997154,
"_source": {
"name": "Sony Travel Speaker 3",
"description": "Precision equipment for professionals"
}
},
{
"_id": "13",
"_score": 4.8997154,
"_source": {
"name": "Apple Travel Speaker 13",
"description": "Premium wireless sound for travel"
}
}
]
}
}How to read this result
The numeric scores show one execution, not permanent expected values. Inspect which field contains travel, then compare the result with an unboosted query to observe the ranking effect.
Combine relevance and filters
A practical search often combines a scored match in must with stock and price restrictions in filter. All conditions remain mandatory, but only the text query contributes to relevance. The example searches gaming and restricts products to in-stock items below 800.
Putting the text clause under filter can return the same matching set while discarding text ranking. Putting business restrictions under must can introduce scoring contributions. Preserve the distinction even when a small sample makes the outputs appear similar.
Try a different scenario
{
"query": {
"bool": {
"must": [
{
"match": {
"name": "gaming"
}
}
],
"filter": [
{
"term": {
"in_stock": true
}
},
{
"range": {
"price": {
"lt": 800
}
}
}
]
}
},
"size": 2,
"_source": [
"name",
"price",
"in_stock"
]
}Elasticsearch response
{
"hits": {
"total": {
"value": 20,
"relation": "eq"
},
"hits": [
{
"_id": "4",
"_score": 2.3304372,
"_source": {
"name": "Logitech Gaming Keyboard 4",
"price": 199,
"in_stock": true
}
},
{
"_id": "14",
"_score": 2.3304372,
"_source": {
"name": "Samsung Gaming Keyboard 14",
"price": 199,
"in_stock": true
}
}
]
}
}How to read this result
Returned products satisfy both filters, and their relative ranking is driven by the text match. total includes every matching eligible product, not just the small returned page.
Key takeaways
- Sort keys are applied in the order written; id resolves ties.
- from = (page − 1) × size; total is not a page length.
- _source changes the payload, not matching or sorting.
- Boost changes a field contribution; scores vary with the corpus.
- Keep scored text in must and eligibility in filter.
Official reference
Ready to put this into practice?
Confirm reading to save your place. Reading and exercise progress are tracked separately in this browser using your guest cookie.