On this page
What you need first
This lesson uses ideas from these earlier lessons. Read them first if any idea is new to you.
01Why interviewers ask this
Users type a few words and expect the best matches fast. A food app has millions of dishes. The interviewer asks: "Design search for this app", or "Why not use a database query?"
A weak answer says "use Elasticsearch" and stops. Another weak answer says "use LIKE in the database". A strong answer explains why a database index does not help here. It shows how a list of words points to documents, and how results are put in order. It says how the data is split over servers. It also says that search is a copy of the database, and how the copy is kept up to date.
At SDE-2 level you should explain the word index and the ordering of results. At SDE-3 level you should also choose the number of shards, and explain what the user sees when the copy is late.
02The idea in plain words
Look at the index at the back of a book. For each word it lists the pages where the word appears. To find "paneer", you do not read every page. You look at one line.
A search server does the same for all its documents. A document is one item that you can find, such as one dish listing. A term is one word after cleaning.
Why a database index is not enough
A B-tree (lesson 20) can help with =, ranges and LIKE 'paneer%'. It does not help with LIKE '%paneer%', because the start of the text is not known. The database must read every row. This is a full scan. A database query also has no idea of "best match". It cannot rank, and it does not know that "dishes" and "dish" are the same word.
The inverted index
An inverted index lists, for each term, the ids of the documents that contain it. Each list is a postings list. It is sorted by id. First the text is analysed:
- Split the text into words.
- Make them lower case.
- Reduce each word to its root: "dishes" becomes "dish". This is stemming.
The same steps run on the query, so the words match. Here are four documents:
- Paneer butter masala
- Butter chicken
- Paneer tikka
- Masala dosa
The index is:
| Term | Documents |
|---|---|
| paneer | 1, 3 |
| butter | 1, 2 |
| masala | 1, 4 |
| chicken | 2 |
| tikka | 3 |
| dosa | 4 |
The query "paneer butter" needs two lists: [1, 3] and [1, 2]. For "both words" (AND), walk down the two sorted lists together. Keep an id that is in both. The result is [1]. For "either word" (OR), the result is [1, 2, 3]. The walk takes at most as many steps as the two lists have ids. It never looks at the other documents.
The cost is the work at write time. A new or changed document changes the lists of every word in it.
Ranking
An OR query can match thousands of documents. The user reads the top ten, so the order matters. Three ideas decide a score:
- Term frequency. A document that has the word more often is a better match. But the gain slows down: the tenth mention adds less than the second.
- Inverse document frequency (IDF). A word that is in few documents tells more than a word that is in most documents. "Special" is in many dish names, so it tells little. A simple version: IDF = log10(N / n), where N is the number of documents and n is the number that contain the word. (Elasticsearch uses a slightly different formula with the same idea.)
- Length. A match in a short name counts more than a match in a long text.
Elasticsearch uses a method called BM25 that combines these three. Its settings k1 (how fast the gain slows down, default 1.2) and b (how much length matters, default 0.75) can be changed. Real search also adds business signals such as rating or a paid boost. A filter such as "vegetarian only" or "under Rs 300" keeps or removes documents and does not change the score.
Typos. A user may type "panner". A fuzzy search also accepts terms that are a few letters away: one letter added, removed or changed counts as one step. It costs more work, so use it only when a search finds few results.
Elasticsearch in practice
Elasticsearch is a search server. It stores documents as JSON and builds the inverted index for you. Its words are a little different:
- An index here is a named collection of documents, like a table. It is not a database index.
- A mapping gives each field a type. A
textfield is analysed, so you can search words in it. Akeywordfield is kept whole, for exact filters and sorting. - An index is split into primary shards. A shard is a small, complete search index. The shard of a document is hash(routing value) mod the number of primary shards. The routing value is the document id by default. This is the hash mod of lesson 43, so the number of primary shards is fixed when you create the index. A split tool exists, but it only multiplies the number by a set factor, and the index must be read-only while it runs.
- Each primary shard has replicas (lesson 39). They answer searches and survive a server failure.
A search goes to one server, the coordinating node. It sends the query to one copy of every shard. Each shard returns its best results. The coordinating node merges them and returns the top results. This is scatter-gather (lesson 43). Many tiny shards slow each search, because it must ask all of them. Elasticsearch advises 10 to 50 GB per shard, and fewer than 200 million documents.
Paging. Page 1,000 of 10 results needs the top 10,000 from every shard. The default limit for from plus size is 10,000. For deeper paging, use a cursor (lesson 10).
Near real time. A new document is searchable after the next refresh, which runs about every second. Files are small and never changed. A changed document is written again as a new version.
Keeping search in step with the database
The database is the truth. The search index is a copy. There are three ways to fill it.
- Write to both. The app saves in the database and then in the search server. One of the two can fail, and the two copies stay different. This is the dual-write problem of lesson 64.
- Outbox or change data capture (lesson 64). The change is recorded in the same transaction as the data. A small indexer program reads the changes and sends them to the search server in batches. A batch may arrive twice. Saving a document under its id again gives the same result, so the repeat is safe (lesson 61).
- Full rebuild. Build a new index from the database. Then move an alias, a name that points at an index, to the new one. Use it for a new mapping or to repair a difference.
The copy is a little late (lesson 40). A dish that was sold out a moment ago may still show. So the app reads the final facts, price and stock, from the database.
When the data is small and the queries are simple, Postgres full-text search (a tsvector column with a GIN index) may be enough. It has no second system and no sync.
Stuck on something? The AI tutor has read this lesson. Ask it with a free account
03Pause and predict
Pause. Guess first.
A food app has 2,000,000 dishes. The word "paneer" is in 40,000 dishes. The word "butter" is in 30,000. A user searches for dishes with both words. How many ids must the search server read: about 70,000, about 2,000,000, or about 30?
Write your guess before you read on. The answer is in the worked example.
04Worked example
SwadKart searches its dish listings.
Assumptions (made up to teach with, not real data):
- 2,000,000 dish listings of 1 KB each: 2 GB.
- A full scan reads 1 GB per second (as in lesson 20).
- An id takes 4 bytes. Real engines compress the lists, so real lists are smaller.
- "special" is in 600,000 dishes.
- The index and stored text together are 2 times the raw size: 4 GB. In two years the data is 10 times bigger.
Step 1: the LIKE query. It reads 2 GB. At 1 GB per second that is 2 seconds. With 200 searches per second, the database would need 200 x 2 = 400 seconds of reading every second. That is not possible.
Step 2: the inverted index. The "paneer" list has 40,000 ids x 4 bytes = 160 KB. The "butter" list has 30,000 x 4 = 120 KB. Together 280 KB. A scan reads 2 GB = 2,000,000 KB, which is 2,000,000 / 280 = 7,143 times more. The walk takes at most 40,000 + 30,000 = 70,000 steps. This answers the predict box: about 70,000.
Step 3: ranking. IDF = log10(2,000,000 / n):
- paneer: log10(2,000,000 / 40,000) = log10(50) = 1.70
- butter: log10(2,000,000 / 30,000) = log10(66.7) = 1.82
- special: log10(2,000,000 / 600,000) = log10(3.33) = 0.52
The query is "special butter paneer". Dish A is "Paneer butter masala". Dish B is "Special special special thali". With term frequency times IDF, A scores 1.70 + 1.82 = 3.52. B scores 3 x 0.52 = 1.57. A wins, because it has two rare words.
BM25 slows the gain from repeated words. With k1 = 1.2 and an average-length text, the term factor for 1, 2, 3 and 10 mentions is 1.00, 1.375, 1.571 and 1.964. It never goes above k1 + 1 = 2.2. Dish B now scores 1.571 x 0.52 = 0.82.
Step 4: shards. The index is 4 GB today and 40 GB in two years. The number of primary shards is fixed. We choose 3. Each shard is 4 / 3 = 1.3 GB today. In two years it is 40 / 3 = 13.3 GB, inside the advice of 10 to 50 GB. With one replica each there are 6 shard copies, 2 on each of 3 servers. One search asks 3 shard copies, one for each shard. Each returns its top 10, and the coordinating node merges 30 results into 10. For page 1,000, each shard returns its top 10,000, and the coordinating node sorts 3 x 10,000 = 30,000. Page 1,001 would pass the limit of 10,000.
Step 5: keeping it in step. Assume 5% of the listings change each day: 100,000 changes, which is 100,000 / 86,400 = 1.2 per second. The indexer sends a batch every second, and the refresh takes about 1 second. So a change is searchable in about 2 seconds, plus any delay of the change feed. A restaurant uploads 50,000 dishes. Assumption: the server takes 5,000 documents per second. That takes 50,000 / 5,000 = 10 seconds. A full rebuild takes 2,000,000 / 5,000 = 400 seconds, about 6.7 minutes. Then the alias moves to the new index.
05Trade-offs
- A search server solves finding by words and ranking at low cost: 280 KB read instead of 2 GB. It costs a second system, a copy that is a little late, and extra work on every change. I would use Postgres full-text search first when the data is small.
- More shards solve size and write speed. They cost a slower search, because every search asks every shard, and a fixed number. I would choose shards for the size in two years, and rebuild into a new index when the number must change.
- Ranking by words only solves the first version. It costs poor results when popularity or distance matters. I would add business signals.
06Common misconceptions
"Elasticsearch can be my main database." The index is a copy with a refresh delay. Keep the truth in the database.
"More shards make search faster." A search asks every shard. In the example, 3 shards meant 3 requests for each search. Many tiny shards slow it down.
"The dish with the most mentions of the word is the best match." In the example, a rare word scored 1.70 and a common word 0.52. Repeats also count less each time.
07How to say it in the interview
Say it like this
I would keep the database as the truth and build an inverted index in a search server, ranked with BM25. A change feed keeps the index current, and the app rechecks stock in the database because the index is a little late.
08Check your understanding
- What is an inverted index? What does IDF say about a word?
- A site has 5,000,000 documents of 1 KB. A scan reads 1 GB per second. One word is in 25,000 documents and another in 10,000. (a) How long does a scan take? (b) How many KB of ids (4 bytes each) must the search read for both words? (c) Which word has the higher IDF? Use log10.
- A seller marks a dish as sold out. It still shows in search for 10 minutes. Why can this happen, and what do you change?
09Answers
Show the answers
- It lists, for each term, the sorted ids of the documents that contain it. IDF says that a word in few documents tells more than a word in many. The score of a rare word is higher.
- (a) 5,000,000 x 1 KB = 5 GB. At 1 GB per second, 5 seconds. (b) (25,000 + 10,000) x 4 bytes = 140,000 bytes = 140 KB. (c) log10(5,000,000 / 25,000) = log10(200) = 2.30. log10(5,000,000 / 10,000) = log10(500) = 2.70. The word in 10,000 documents has the higher IDF.
- The index is a copy. The change goes through the feed and the indexer, and a backlog there delays it. The refresh adds about 1 second. Check the stock in the database when the user opens or orders the dish. Alarm on the age of the oldest change in the feed.
10Sources
- Default similarity BM25, k1 = 1.2 and b = 0.75: Elasticsearch, similarity settings, https://www.elastic.co/guide/en/elasticsearch/reference/current/index-modules-similarity.html (opens in a new tab) (checked 20 Oct 2026).
- Shard routing formula (hash of the routing value mod the number of primary shards): https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-routing-field.html (opens in a new tab). Shard size advice (10 GB to 50 GB, under 200 million documents, many small shards cost more): https://www.elastic.co/guide/en/elasticsearch/reference/current/size-your-shards.html (opens in a new tab). Both checked 20 Oct 2026.
- Refresh about every second, near real-time search: https://www.elastic.co/guide/en/elasticsearch/reference/current/near-real-time.html (opens in a new tab). Defaults
refresh_interval1s andmax_result_window10000: https://www.elastic.co/guide/en/elasticsearch/reference/current/index-modules.html (opens in a new tab). Checked 20 Oct 2026. textandkeywordfield types: https://www.elastic.co/guide/en/elasticsearch/reference/current/text.html (opens in a new tab) (checked 20 Oct 2026).- Postgres full-text search (
tsvector,tsquery, ranking, GIN index): https://www.postgresql.org/docs/current/textsearch-intro.html (opens in a new tab) (checked 20 Oct 2026). - The simple IDF, log10(N / n), is a teaching version. BM25 uses a different formula. The term factor in Step 3 is the classic BM25 term part: tf x (k1 + 1) / (tf + k1). It is for a text of average length, where b has no effect. Since Lucene 8.0, Elasticsearch leaves out the factor (k1 + 1) (2.2 here). All scores become 2.2 times lower and the order stays the same: https://issues.apache.org/jira/browse/LUCENE-8563 (opens in a new tab) (checked 20 Oct 2026).
- Fuzzy search by edit distance: https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-match-query.html (opens in a new tab) (checked 20 Oct 2026).
- All document counts, sizes, speeds and rates are assumptions made for teaching. The BM25 factors are calculated: 2.2 / 2.2 = 1.00, 4.4 / 3.2 = 1.375, 6.6 / 4.2 = 1.571, 22 / 11.2 = 1.964.
Try it now, no sign-up
Related lessons and practice
Ask the AI tutor about this lesson
The tutor has read this lesson. Ask in your own words, any time. Free account, 20 questions a day.
Reading never needs an account. The account is for the tutor, the quiz, your notes and your progress.