Skip to content

The filter says 1,200 jackets. Why can search find only 1,130?

Search Returned an Answer. Was It the Right One? · Part 5 of 6 · 6 min read

Read the worked explanation ↓

Interview question. A marketplace offers category, brand, color, and price filters over 100 million products. A search for "winter jacket" displays a sidebar count of 1,200 for red jackets. After the shopper selects Red, the result page reports 1,130 matches. Engineers propose increasing the shard count to make counting faster. What could cause the disagreement, and how would you design accurate, useful facets at this scale?

Before touching infrastructure I would inspect how the two numbers were computed. Are they counting the same search population, with the same query, permission rules, inventory exclusions and index snapshot? Does the sidebar mean "red jackets among the current winter-jacket results" or "all red jackets in the catalog"? Does selecting Red affect the count for Red itself or only other facets? Two individually valid queries can disagree simply because the product requirement was never defined.

Facet counts are not decoration. A user deciding whether a filter is worth clicking treats the numbers as information about available choices. We need a contract for the population being counted, not just a fast query.

Facet aggregation counts match a chosen filtered population, while shard-local top bucket cutoffs may lose contributions before the coordinator merges counts.
Facet aggregation counts match a chosen filtered population, while shard-local top bucket cutoffs may lose contributions before the coordinator merges counts.
Think first

Design a multi-tenant hotel search page with destination, star rating, refundable status, and neighborhood facets. Some facets must count the entire matched population and others must show counts excluding their own selected filter. Give a precise query contract, correctness tests, and a fallback if exact counting is too expensive.

Take a few minutes to form an approach, then compare it with the worked explanation.

Reveal the worked explanation

A facet is an aggregation over a set of matching documents

Search ranking and facet aggregation do different work. Ranking may return only the top ten documents. The sidebar must often count all matching documents by category or color, even those far below page one. Implementing facets by looking at the first ten hits would be obviously wrong. The system needs an aggregation over the intended document set.

For exact-match filters such as brand, color, and product type, keyword-like fields with column-oriented doc values are a natural fit for many engines. They support sorting and aggregation without requiring those fields to be analyzed like prose. An analyzed text field for "dark red" might contain separate terms dark and red and is not automatically suitable for a stable facet value. Elasticsearch documents that terms aggregations generally use keyword, numeric, boolean, IP, or similar aggregatable field types, with important memory implications if text fielddata is enabled.

Now define the filtering semantics. A search for "winter jacket" might apply query text, region availability, current tenant visibility, and a not-deleted condition. Those are mandatory constraints. If the shopper selects Brand A, we may show color counts within Brand A, while the brand list may show how many matches other brands would have if the current brand selection were removed. The latter is often called disjunctive faceting. It is a product choice, not an engine default.

In Elasticsearch, the difference between a filter in the search query and post_filter can matter. A post_filter can narrow the returned hits after aggregations have been calculated. This can be useful for a specific facet UI, but a careless choice shows counts for a larger population than the visible results. Write tests that state the intended population for each sidebar number.

Distributed counting can introduce another disagreement

Assume twenty shards compute a top-ten list of categories by document count. Each shard has thousands of categories. A category might rank eleventh on every shard and never be sent to the coordinator, yet still be a global leader: each shard can have ten different local-only categories with 101 documents each, while the shared category has 100 on each of twenty shards, or 2,000 overall. Returning more buckets per shard reduces that risk but increases per-shard work, network payload and coordinator memory.

Elasticsearch's terms aggregation requests more candidates than the final size through shard_size. Its documented default is approximately size * 1.5 + 10. Under the usual descending-count order, result counts can still be approximate because some shard contributions were not sent. The response exposes fields such as doc_count_error_upper_bound and sum_other_doc_count, with qualifications. A zero error in a supported case is meaningful. A nonzero upper bound should not silently be displayed as an exact inventory count.

Increasing shard count does not inherently improve accuracy. It may increase opportunities for local truncation and coordinator fanout, depending on data distribution and configuration. And increasing size indiscriminately can push aggregation memory and bucket limits beyond acceptable bounds. We should choose between top categories with documented approximation and an exact answer for a known selected category.

An exact "How many red jackets?" count can be computed with a filtered counting query over all matching shards and a consistent search view, assuming the underlying count API semantics and filters support that guarantee. That can cost more than asking for the largest ten facets. Product requirements should decide which counts must be exact and which may be estimates marked as such.

Make the disagreement observable

My investigation would capture one query ID and the full effective query and filter set for both the sidebar and clicked result page. I would log which indices, shard copies, routing parameters, refresh snapshots, aggregation size, shard_size, and error metadata were involved. Then run a controlled test on a frozen dataset with known counts. That separates approximation from freshness and filter mismatch.

Privacy cannot be an afterthought. If user-specific availability or tenant policy is applied only when fetching hits, a global facet count can leak that hidden products exist. Mandatory authorization constraints must apply to aggregation as well as retrieval, and the security boundary should not rely solely on a UI filter.

Inventory changes can also make the first and second pages disagree over time. For a bounded shopping interaction, decide whether to favor freshest counts or a stable point-in-time view. A PIT can stabilize search snapshots for a session when appropriate, though it does not validate authoritative checkout inventory and carries resource cost.

The answer I would look for in an interview is define the counted population, use appropriate columnar facet fields, understand shard-local candidate truncation, and choose explicit accuracy semantics. Shard capacity is a scaling concern, not a substitute for these decisions.

Go deeper

In Elasticsearch, terms aggregations are shard-local candidate collection followed by coordinator reduction. For descending _count, shard_size controls candidate breadth and can reduce missing counts. Other sort orders have different and sometimes unbounded error behavior. composite aggregations are useful for paging through many distinct buckets rather than pretending an unbounded top-terms result is free. post_filter changes hits after aggregations, so pair it with deliberately structured facet definitions. An exact facet count must still use the intended query scope and security filters.

Check your reasoning

  • Define the specific filtered population for each facet.
  • Separate top hits from aggregation over all matches.
  • Explain keyword fields and doc-values aggregation needs.
  • Identify terms shard_size approximation and response error metadata.
  • Protect tenant privacy in counts and choose exact counts only where needed.

Common mistake: Aggregating the ten displayed search hits gives the total count of red jackets.

Facet counts disagree with clicked search results. Which design addresses their correctness?

Read the answer explanation

Define query population, aggregate appropriate fields and expose approximation explicitly — Facet counts require the intended population and sometimes shard-local bucket reductions approximate their totals.

Apply it elsewhere

A medical research portal displays case counts by rare condition. A visible exact number for a tiny group could reveal private information. Design facet aggregation that balances query accuracy, cost, privacy thresholds, and permitted drill-down.

Compare your approach

Separate ranked hits from the population aggregated, specify disjunctive versus conjunctive filters, explain keyword/doc-values fields, quantify shard candidate truncation, distinguish approximate from exact counts, and enforce authorization inside aggregation.

One more constraint

Can increasing the `terms` aggregation's `shard_size` reduce count approximation while also adding network and coordinator work?

Quick revision

Facet counts aggregate an explicitly chosen matched population, not the first page of search hits. Check query filters, post-filters, authorization, freshness and snapshots before blaming infrastructure. Keyword-like fields with doc values commonly support facets. Distributed terms aggregation may truncate shard-local buckets, so counts can be approximate even after merge. Tune shard_size with resource budgets, inspect error metadata, and compute exact selected counts when the product truly requires them. Never expose hidden tenant data through aggregation.

References