Design Facebook Post Search
Patterns, Technologies, and Concepts
Key Takeaways
Requirements
You demonstrated strong foundational thinking on requirements gathering, correctly identifying latency thresholds, consistency tradeoffs, and indexing freshness without any major gaps.
-
For search systems, 500ms is the standard latency threshold to target. Users abandon search results that feel slow, so keeping end-to-end query time under 500ms is a concrete benchmark to cite in interviews.
-
Social search systems favor availability over consistency in the CAP tradeoff. It is acceptable for search results to be slightly out of sync across nodes because showing a user slightly stale results is far better than showing them an error.
-
Index freshness is a key operational requirement for real-time social search. New posts should be searchable within about 1 minute, which means your design needs an ingestion pipeline fast enough to index content near real-time rather than in batch jobs.
-
Tiered data access is a useful pattern to mention in search design. Recent or popular content can be kept in fast storage like an in-memory cache, while older posts can tolerate slower retrieval from cheaper storage, reducing cost without hurting the typical user experience.
Core Entities
You demonstrated a solid understanding of the core data entities needed for a social search system, correctly identifying Posts, Likes, and Users as the foundational building blocks.
-
When designing a search system for social content, your three core entities are Posts (the searchable object), Likes (signals for ranking by popularity), and Users (the actors who create and interact with content). These map directly to your database tables and drive your indexing strategy.
-
Likes are not just a vanity metric in system design. They serve as a popularity signal that feeds into search result ranking algorithms. Storing like counts or aggregating them efficiently is important for sorting search results by relevance or trending status.
API
You demonstrated strong API design instincts across both attempts, nailing the core endpoints and pagination patterns with only minor naming conventions to polish up.
-
Use /posts instead of /feed for a keyword search endpoint. The path /feed implies a personalized timeline curated for a specific user, while /posts correctly signals you are querying a general posts resource. RESTful paths should reflect the resource being accessed, not the presentation layer.
-
The standard abbreviation for descending sort order is DESC, not DES. Using DESC keeps your API consistent with SQL conventions and database query language that other engineers will immediately recognize, reducing ambiguity in your design.
High Level Design
You demonstrated solid instincts across all three attempts, keeping the design simple and building on it cleanly, though you had some small but important gaps around data consistency and being explicit about how data flows between components.
-
When you have a Likes table and also store a like count on the Posts table, you have two sources of truth, which causes confusion. Pick one approach and stick to it. Either store a like_count column directly on Posts and increment it on each like action, or use a separate Likes table keyed by postId and derive the count from that table. In an interview, state your choice clearly so the interviewer knows where like count comes from.
-
When you add a derived or computed field to a schema, like a search_vector for full text search, you need to explain when and how it gets populated. The answer here is that the Post Service writes both the raw content and the derived searchable representation at post creation time. This keeps search results in sync without needing a separate sync job.
-
When sorting query results in a search flow, name the mechanism explicitly. Saying the Search Service adds an ORDER BY createdAt or ORDER BY likes to the database query is much clearer than implying it. Interviewers want to hear you connect the requirement to the specific database operation that satisfies it.
Deep Dives
You demonstrated strong core design instincts across most of this section, but had two notable gaps where you gave no answer at all, specifically around keeping Redis sorted sets fresh under high like volume and handling cache misses for pruned keyword indexes.
-
When likes are updating constantly for a viral post, updating Redis on every single like event is too expensive. Use a batching strategy where the Like Service aggregates like counts in memory over a short window (e.g. 5 seconds) and then flushes the aggregated delta to the database. After the flush, either CDC or a direct write from the Like Service updates the Redis sorted set scores. This trades slight staleness for dramatically lower write pressure.
-
A popular post can appear in many keyword indexes (one sorted set per keyword in the post). This means a single like update fans out to many Redis writes. Cap the number of keywords extracted per post at ingestion time (e.g. top 5 most relevant keywords) to bound the worst-case fan-out cost. This is a concrete and practical way to control write amplification.
-
When a Redis keyword index has been pruned due to low usage, the Search Service needs a fallback path. The flow is: detect a Redis miss, check S3 for a pre-stored index snapshot, then fall back to the Postgres GIN index if S3 has nothing. After serving the result, optionally re-populate Redis with a lightweight cache entry so repeated rare queries get faster over time. This lazy loading pattern is called write-on-read.
-
For very broad hot queries (e.g. searching a common word like ‘love’), pagination alone does not make the query fast because the backend still has to find the first page from a massive match set. The right answer is to serve these from a precomputed cached result in Redis. Your pre-sorted Redis sorted sets (scored by time or likes) let the Search Service grab the top K post IDs directly without scanning or resorting, giving you predictable low latency for the hottest queries.