X feed selection: what happens after ranking | Gyo
Why a high score is not a place in your feed
Ranking is followed by selection, visibility checks, conversation deduplication and feed assembly. A high-scoring candidate still has several decisions ahead of it.
Gyo
A post can reach the ranker, receive a score and survive the first selection, yet still be absent from the feed. Ranking is an important decision along the way, but the response has more work left to do.
The Phoenix pipeline runs post-selection hydration and five registered filters before truncating its post results. An outer For You pipeline then blends those results with other feed items. Following both layers explains why a high score is not a reserved place on the screen.
This part completes the request walkthrough. We have looked at weights, eligibility filters, candidate sources and predictions. Now we can follow one request through the remaining decisions and put those pieces in order.
Keep one request in view
Imagine the same reader opening X again. Several sources have returned candidates for that request. Their data has been filled in, and the enabled pre-scoring filters have removed some of them. The survivors enter the scorer stage.
The shared runner makes the overall order explicit: query hydration, retrieval, candidate hydration, filtering, scoring, selection, post-selection hydration, post-selection filtering, and result truncation. It then finalizes results and starts side effects.
This is a request-level process. A post can participate in many different requests with different viewers, history and competing candidates. There is no single global race in which the platform assigns one permanent rank to every post.
For the reader in our example, the question is therefore quite specific: which candidates survive this request, in what order, and in which kind of feed item?
The score used for selection can change along the way
As part four showed, a model prediction and a final selection score are different fields. VMRanker prepares local weighted values, applies its local cold-start handling and calls the ranking service. Returned candidate scores can replace the local scores. A failed call falls back to the prepared local results; a candidate missing from a successful response retains its local result.
FAQ
Does a high X ranking score guarantee that a post is shown?
No. Selected candidates still pass through post-selection hydration, filters and result truncation. The outer For You pipeline then blends post results with other item types. A server response alone also does not establish a client-recorded impression.
Does X select 50 posts and always show 35?
The ordinary Phoenix selection cap is 50 and its post-result cap is 35 in the examined snapshot. Optional carousel extras and intervening filters change the path. These are stage limits, not guaranteed response counts or a candidate survival probability.
Can a reply lose its place to a lower-scoring unrelated post?
The conversation-deduplication filter keeps the highest-scored candidate per derived conversation key. A reply can lose to another candidate in its group while an unrelated, lower-scored candidate remains. This is separate from same-author score adjustments.
Are ads and follow suggestions ranked by the same post score?
The outer blender partitions item types, chooses an ad-blending implementation and inserts supported modules. Its implementation does not perform one global sort of ads, follow suggestions and organic posts using the same Phoenix action score.
Two later components make the distinction even more visible. PageDecodeRerankScorer can place decoded picks first by reassigning the existing ordered score values. PageRlExplorationScorer can sample an order within a top-scoring pool. It skips that sampling when page decoding is enabled and decoded slots are present.
Both switches have disabled defaults in this snapshot. They are conditional mechanisms, not a reason to assume every feed is randomly reordered.
A small example helps with the page-decode helper. Suppose A, B and C have scores 9, 6 and 3. If the decoder picks C for the first slot, the helper can assign those score values to C, A and B respectively. The values remain 9, 6 and 3, but their owners change. A later score sort now puts C first. This is an invented example checked against the helper, not a measured production response.
When comparing “scores,” we therefore need to know which field, at which stage, under which switches. A raw prediction, a weighted value and a post-reranking selection score answer different questions.
Fifty is the ordinary selection cap, not the initial pool
TopKScoreSelector sorts candidates using their current score field, treating a missing score as negative infinity. Its ordinary cut keeps up to TOP_K_CANDIDATES_TO_SELECT candidates. The constant is 50.
By then, retrieval and pre-scoring filtering have already happened. Fifty is not the size of the original retrieved pool, nor a guaranteed test audience for each post.
There is also an explicit exception to the ordinary selection count. When the video-carousel path is enabled, the selector can append extra video candidates drawn from the non-selected set, using carousel-specific scoring. That prepares material for a later module; it does not promote every appended video into the ordinary post list.
The selector returns selected and non-selected candidates separately. The next stage receives the selected set. It does not continuously pull a replacement from the non-selected set every time a later filter removes something.
This becomes important when looking at the final count. A selection cap is the most that an ordinary selection step can keep, not a promise that the same number will survive everything afterward.
Some checks wait until after selection
The pipeline registers ten post-selection hydrators. They fill in information for visibility filtering, advertising brand safety, relationship and topic context, video media, embeddings and ancestor-video checks, among other work.
These tasks operate on the selected set at this point in the code. That tells us where they run; it does not, by itself, measure how much work this arrangement saves or establish that every registered hydrator is active.
Removes candidates with video-bearing ancestors when video exclusion is requested
DedupConversationFilter
Keeps the highest-scored candidate for each derived conversation key
VideoCarouselFilter
Separates carousel extras and prepares a video module when enabled
The number five counts registered filters. As with the earlier 21, enable conditions matter. It would be misleading to add the two counts and say every post always passes 26 active tests.
A visibility action is not just a lower score
VFFilter examines the visibility action attached to a candidate. On this path, Drop, Tombstone and NotEvaluated remove the candidate. Allow, Interstitial, Avoid and Downrank do not cause this particular filter to drop it. An absent action is not a drop here either.
Those distinctions prevent two opposite mistakes. A large ranking score does not override a drop decision in this function. But seeing the word Downrank in an action does not mean this filter deletes the candidate or directly changes its score. To understand how that action affects the rest of serving, we would need to follow the relevant consumers.
The small filter is also not the whole visibility policy. It consumes an action produced by other work. Its match statement does not explain every label, rule or service decision that led to that action.
AncillaryVFFilter uses a separate drop_ancillary_posts flag. AncestorVideoFilter is enabled by video exclusion and removes candidates whose ancestor-video flag is true. A post’s related material can therefore affect whether it remains, even after its own ranking step is complete.
Replies can compete within a conversation
The earlier duplicate checks dealt with post identity and repost relationships. DedupConversationFilter does something different after selection.
It derives a conversation key from the minimum ancestor ID when ancestors exist, otherwise from the candidate’s original post ID. For each key, it keeps the candidate with the highest score. Equal scores preserve the first occurrence. A missing score is treated as zero for this comparison.
Suppose two selected replies share the same derived conversation key. A has score 9 and B has score 8, while an unrelated C has score 2. This filter keeps A and C and removes B. B can lose its place despite scoring higher than C, because the decision is being made within a conversation group rather than by another unrestricted global top-N sort.
The example says nothing about a universal limit on replies from an author or a penalty for writing a thread. It describes the actual grouping key and comparison at this stage. It is also different from the same-author adjustment in the first article: one modifies ranking behavior around authors, while this filter chooses a survivor per derived conversation key.
A video module has its own selection work
The last post-selection filter, VideoCarouselFilter, handles the optional carousel path. It separates candidates marked as carousel extras from the kept list. It treats the first RESULT_SIZE ordinary candidates as the main feed, and considers later candidates and extras for the video module.
The module-selection call uses carousel scoring, configurable brand-safety handling and optional deduplication settings involving authors, semantic IDs and embeddings. This is a useful counterpoint to the identity-only duplicate functions in part two: there are other deduplication mechanisms elsewhere, and their scope must be read separately.
The filter stores the selected carousel videos in query state. Those module choices have a separate route into the eventual feed. Being removed from the ordinary candidate list as a carousel extra therefore does not, by itself, establish that the item can never appear anywhere in the response.
The outer blender adds another condition: its module conflict rule drops a carousel when Who to Follow modules are present. The post ranker’s output alone cannot explain the presence of every module on the screen.
If more than 35 ordinary candidates remain, the tail is moved into the non-selected list. If fewer remain, this step does not refill the list from candidates rejected by the earlier selector. We have a cap, not a guaranteed full page.
This is why dividing 35 by 50 is not a post’s survival probability. The numbers are limits at different stages, with variable inputs and conditional processing between them. Neither measures how often a particular candidate reaches the user.
The scored-post server converts surviving candidates into scored-post response records and separately exposes an available video carousel. The post pipeline has completed its part of the job. The full For You response is still being assembled.
The outer feed is a blend of item types
The For You candidate pipeline retrieves scored posts alongside sources for ads, Who to Follow, prompts, push-to-home content, frames and a feed survey. Its ScoredPostsSource wraps post-pipeline output into feed items, including the optional carousel.
BlenderSelector partitions those item types, chooses an ad-blending implementation from configuration, blends posts and ads, then inserts the other supported items. This is not one global score sort in which an ad, a follow suggestion and an ordinary post all use the same Phoenix action score.
The outer size constant has separate allowances: 35 post results, five module slots and up to eight frames, totaling 48 feed items. Those are configured capacities, not a guarantee that all kinds of items will be present in every response.
There can be later checks here too. The For You implementation registers an ad-adjacent served filter after blending, with request-specific conditions in its implementation. The five post-selection filters discussed above belong to the inner Phoenix pipeline; they are not the last possible processing anywhere in X.
Finally, a server-side result is not proof that a person saw it. Client rendering, scrolling and the measurement definition still separate a returned item from an observed impression. This walkthrough describes the public serving path, not the complete client analytics contract.
The request also leaves information for later requests
The runner starts side effects after preparing its result. One candidate-cache side effect gathers selected and non-selected candidates with positive weighted scores, sorts them by weighted score, caps the list and writes a compressed payload with a 180-second TTL when its production and fresh-request conditions hold.
That gives a concrete connection to part three’s cached retrieval path. A candidate that was not selected now can still be stored for a later request. The cache is not simply a copy of the 35 ordinary posts just returned, and its weighted-score order need not be the final order produced by optional reranking.
The outer pipeline also registers served-history and logging work. These records and caches are part of why successive requests need not begin from a blank slate. Their existence does not mean an author can reconstruct every individual decision from the public view counter.
Reading a quiet post without inventing a cause
We can now ask more useful questions about a post with low impressions. Was it retrieved for the requests in question? Did the required data arrive? Which eligibility checks ran? Were predictions available? Which ranking path and later scorers ran? Did the post pass selection and post-selection filtering? Was it returned as an ordinary post or considered for a module?
Each question points to different evidence. Retrieval records answer something different from model outputs. A final ranking position does not expose a visibility action. Server response records do not establish what the client counted as viewed. An aggregate impression total cannot uniquely identify all of these intermediate states.
That completes the request walkthrough. The weights explain part of a score; the filters explain some removals; retrieval explains how the comparison set forms; predictions explain the quantities being weighted. The final feed adds selection, visibility decisions, grouping, modules and state carried between requests.
For an author, this is a better map of the uncertainty. It keeps us from treating every quiet post as a coefficient problem, every fluctuation as an experiment, or every configured limit as a guaranteed audience. Diagnosing a specific case still needs evidence from that case, but we now know where to look.
The next two articles look more closely at visibility policy and cold-start handling, where viewer context and limited evidence introduce further conditions.
Source and verification notes
Source-checked on November 3, 2026, against revision b220448, then upstream HEAD. This article uses the newer revision for post-selection behavior, including its video-carousel implementation. The original revision cited in earlier parts has its own verification notes; unchanged stage names should not be read as proof that every implementation is identical across revisions.
The page-decode reassignment and conversation-deduplication examples were checked using isolated extracts of the published helpers. Counts, constants and registration order were checked against the linked source. This is a source walkthrough, not a full-system execution or a measurement of production filter rates, ranking quality or client impressions.