API Design
In one line: the parameter list of a single call determines what the index has to support. Read search carefully and the next six lessons become predictable.
Search
search(category, user_location, radius)
| Parameter | Description |
|---|---|
category | The type of search the user makes — restaurants, cinemas, cafes, and so on |
user_location | The location of the user who is searching |
radius | The specified radius where the user is trying to find the required category |
Returns a JSON object listing items in the category within the specified radius. Each entry includes the place name, address, category, rating, and thumbnail.
And searching by name:
search(name_of_place, user_location, radius)
The order of the parameters is the design decision
search(category, user_location, radius) could be answered two ways, and which one you pick determines the whole architecture.
Filter by category, then by distance. Take every cafe on the platform, then keep the near ones. There are perhaps millions of cafes worldwide, and you would compute distance to all of them.
Filter by location, then by category. Take everything near the user — a few hundred places — then keep the cafes.
The second is obviously right, and the reason is worth naming precisely: location is the far more selective predicate. A radius of 5 miles eliminates 99.99% of 500 million places. "Category = cafe" eliminates maybe 90%.
Index on the selective dimension. That is why every lesson from here on is about spatial indexing and none is about category indexing — category is a cheap post-filter over a small candidate set.
It also explains something that looks like an omission. The quadtree stores only PlaceID, latitude, and longitude — no category at all. The category filter happens later, when the system looks up those IDs in the relational database. The spatial index answers "which places are near?"; the database answers "which of those are cafes?"
Whether that split is optimal is a fair question, and Lesson 13 returns to it. A leaf node holding 500 places might yield only 6 cafes, forcing a walk to neighbouring nodes — which the design does handle, but at a cost that category-partitioned trees would avoid at the price of maintaining one tree per category.
Two search signatures, one name — and they need different infrastructure
The design gives search twice with different first parameters, which in most languages is overloading and in an HTTP API would be two endpoints.
They are genuinely different queries:
search(category, ...) | search(name_of_place, ...) | |
|---|---|---|
| Predicate | Category plus proximity | Text match plus proximity |
| Index needed | Spatial | Inverted index on names |
| Result size | Many places | One or a few |
| Ranked by | Rating, then distance | Text relevance |
The second is distributed search's problem with a geographic filter bolted on, and the design never addresses it — no inverted index appears anywhere in the architecture.
That is a genuine gap worth volunteering. Search-by-name needs either a text index that also stores coordinates, or a text search that returns candidates which are then filtered by distance. Neither is hard, but neither is free, and the design's components diagram has no box for it.
Note also that radius is a parameter on both, which is a real product decision: there is no global search. Even a search for "Burger Hut" is scoped to a radius, so you cannot find a branch in another city. Defensible for a local-discovery product, and worth stating as a choice rather than an oversight.
Add a place
add_place(name_of_place, description_of_place, category, latitude, longitude, photo)
| Parameter | Description |
|---|---|
name_of_place | The name of the place, for example 'Burger Hut' |
description_of_place | A description of the place |
category | The category of the place — for example, 'cafe' |
latitude | The latitude of the place |
longitude | The longitude of the place |
photo | Photos of the place. There can be a single or multiple photos |
Add a review
add_review(place_ID, user_ID, review_description, rating)
| Parameter | Description |
|---|---|
place_ID | The ID of the place whose review is added |
user_ID | The ID of the user who adds the review |
review_description | The review of the place |
rating | The rating of the place — for example, 4 out of 5 |
add_place writes to two systems, and the API hides it
add_place looks like one call. It is not — it touches three stores, and Lesson 13 will show why that matters.
add_place -> relational database (the row)
-> blob storage (the photo)
-> the QUADTREE (the spatial index)
The third is the one the signature gives no hint of, and it is the expensive one. Inserting into the quadtree means locating the right leaf, adding the place, and — if that leaf now exceeds its limit — splitting it into four children and redistributing. On a replicated, partitioned tree.
This is the reason the estimation's five new places per day figure matters more than its tiny bandwidth contribution suggests. At five per day, an expensive index insert is completely acceptable. At five thousand per second it would not be, and the design would need a fundamentally different index.
A write path that is cheap only because writes are rare is a legitimate design — but only if you say so. The read-to-write ratio here is roughly ten million to one, and it is what licenses nearly every decision in the chapter: daily rating rebuilds, monthly segment regeneration, expensive splits.
add_review does not include a photo, but the schema and requirements do
The functional requirement says users review places using "text, images, and ratings." The Reviews table description says it "stores reviews, ratings, and photos."
But add_review(place_ID, user_ID, review_description, rating) has no photo parameter, and the Reviews schema in the next lesson has no photo column either — the Photos table links to Place_ID, not to a review.
So review photos are promised in the requirements, mentioned in the storage prose, and absent from both the API and the schema. Small, but it is exactly the kind of inconsistency worth catching, because it changes the data model: photos attached to reviews need a Review_ID foreign key, which the Photos table does not have.
Note also that add_review takes a rating while the Place table has its own Rating column, "calculated based on user reviews." That is a derived value, and Lesson 13's rating calculator is what maintains it — one of the few genuinely asynchronous paths in the design.
Splitting business details from its reviews lets the reviews list paginate on its own, which matters because a popular business has far more reviews than fit in one response. Everything returning a list gets a page parameter.
Key takeaway
search(category, user_location, radius) encodes the chapter's central decision: filter by location first, then category, because location is the vastly more selective predicate — which is why the quadtree stores only ID and coordinates and knows nothing about categories. The two search variants need two different indexes, and the design provides no inverted index for search-by-name. add_place hides a third write into the spatial index, which is expensive but affordable only because there are five new places a day against 60 million searches — a ten-million-to-one ratio that licenses nearly every other decision in the chapter.
Next: the storage schema, where the arithmetic does not quite match the columns.