What signals does the X algorithm learn from?
The live system scores every post by summing nineteen named engagement predictions — the probability you will like it, reply, repost, expand a photo, watch a video to quality-view depth, share it three different ways, dwell on it, quote it, click through to the author's profile, follow the author — and four negative predictions: the probability you will click "not interested," block the author, mute the author, or report the post. The signal names are in the open at the pinned commit. The weight on each signal is not.
The famous 2023 table — "replies are worth 13.5 likes" — is the most-quoted artifact of the
first open-sourcing, and it is historical.
What replaced it is better than a table: it is the live combining function, in the open, in
weighted_scorer.rs. Every post that reaches your For You feed has been through this
exact sum:
49let combined_score = Self::apply(s.favorite_score, p::FAVORITE_WEIGHT) 50 + Self::apply(s.reply_score, p::REPLY_WEIGHT) 51 + Self::apply(s.retweet_score, p::RETWEET_WEIGHT) 52 + Self::apply(s.photo_expand_score, p::PHOTO_EXPAND_WEIGHT) 53 + Self::apply(s.click_score, p::CLICK_WEIGHT) 54 + Self::apply(s.profile_click_score, p::PROFILE_CLICK_WEIGHT) 55 + Self::apply(s.vqv_score, vqv_weight) 56 + Self::apply(s.share_score, p::SHARE_WEIGHT) 57 + Self::apply(s.share_via_dm_score, p::SHARE_VIA_DM_WEIGHT) 58 + Self::apply(s.share_via_copy_link_score, p::SHARE_VIA_COPY_LINK_WEIGHT) 59 + Self::apply(s.dwell_score, p::DWELL_WEIGHT) 60 + Self::apply(s.quote_score, p::QUOTE_WEIGHT) 61 + Self::apply(s.quoted_click_score, p::QUOTED_CLICK_WEIGHT) 62 + Self::apply(s.dwell_time, p::CONT_DWELL_TIME_WEIGHT) 63 + Self::apply(s.follow_author_score, p::FOLLOW_AUTHOR_WEIGHT) 64 + Self::apply(s.not_interested_score, p::NOT_INTERESTED_WEIGHT) 65 + Self::apply(s.block_author_score, p::BLOCK_AUTHOR_WEIGHT) 66 + Self::apply(s.mute_author_score, p::MUTE_AUTHOR_WEIGHT) 67 + Self::apply(s.report_score, p::REPORT_WEIGHT);
The current WeightedScorer combines predicted probabilities for: favorite, reply, retweet, photo expand, click, profile click, video quality view, share, share via DM, share via copied link, dwell, quote, quoted click, continuous dwell time, follow author, not interested, block author, mute author, and report.
These are predictions, not counts. Phoenix — the Grok-based ranker — estimates, for
you specifically, the probability of each action before you have seen the post. Then this sum
turns nineteen probabilities into one number. Read the last four lines again: the formula
explicitly includes the predicted probability that you will click not interested, that
you will block the author, mute the author, or report the post —
predictions whose weights pull the score in the wrong direction for the author.
The model explicitly predicts negative actions — not interested, block author, mute author, report — and these carry negative weights in the final score, pushing down content a user would likely dislike.
Signal by signal
| in the code | in plain english |
|---|---|
| favorite, reply, retweet, quote | The classics — but as personalized predictions, not raw counts. A niche post shown to the right person can out-predict a popular one. |
| photo_expand | Image creators have a named signal: the predicted probability a viewer taps your image to full screen. |
| vqv (video quality view) | Video has a named signal — with a catch in the next section. |
| share, share_via_dm, share_via_copy_link | Sharing carries three separate signals. Posts people send to friends in DMs are measured distinctly from public shares. |
| dwell, dwell_time | Stopping the scroll is a signal twice over — a dwell event and a continuous duration. You earn score from attention even when nobody taps anything. |
| profile_click, follow_author | The discovery signals: posts predicted to make someone visit your profile or follow you score for it. Growth is in the formula itself. |
| not_interested, block_author, mute_author, report | The four negatives: predicted rejection probabilities, weighed against you on every post — covered in depth here. |
The video duration gate
The video quality view signal has an eligibility check the others don't:
72fn vqv_weight_eligibility(candidate: &PostCandidate) -> f64 { 73 if candidate 74 .video_duration_ms 75 .is_some_and(|ms| ms > p::MIN_VIDEO_DURATION_MS) 76 { 77 p::VQV_WEIGHT 78 } else { 79 0.0 80 } 81}
If your video is shorter than the minimum duration, the video-quality-view weight is exactly
zero — that signal contributes nothing, and your post is carried by the other eighteen. The
threshold value lives in the withheld parameters, so we can tell you the gate exists but not
where it sits.
The video quality view weight is applied only when a post's video exceeds a minimum duration threshold; shorter videos receive zero weight for that signal.
After the formula
The weighted sum is not the final word. Two adjustments visible in the same release apply on
top: out-of-network candidates — posts shown to people who don't follow you — have their score
multiplied by an OON_WEIGHT_FACTOR, with a code comment stating the intent plainly:
"Prioritize in-network candidates over out-of-network candidates."
Out-of-network candidates have their score multiplied by an OON_WEIGHT_FACTOR, with the code comment stating the intent: 'Prioritize in-network candidates over out-of-network candidates.' The factor's value is in the withheld params module.
Within a single feed response, candidates from the same author are sorted best-first and each subsequent one is multiplied by (1 \− floor) \× decay^position + floor \— a geometric decay toward a floor, so only an author's top-scored post receives full value per feed load. The decay and floor constants are in the withheld params module.
What the code doesn't say
The weight values. Every p::*_WEIGHT above resolves to a
params module that is absent from the public release — verified across the entire
repository tree at the pinned commit. The nineteen signal names are code-current fact; any
specific number attached to them ("a reply is worth N likes in 2026") is invention. The 2023
values are historical, and the
current ones are withheld.
The numeric values of the current weights are not included in the open-source release: weighted_scorer.rs references a params module (e.g. p::FAVORITE_WEIGHT, p::REPLY_WEIGHT) whose values are not present anywhere in the published repository.
What to do with this
Make things people finish, expand, send to friends, and follow you over — every one of those verbs is a named term in the sum. And mind the negatives: the formula is predicting rejection as attentively as it predicts applause. That half of the equation is the territory the next page covers, and the signal family xDoctor's reputation diagnostics are built to estimate.