Logo
CounterWatch

How Counterwatch calculates hero counters and synergies

·Updated |OverwatchMarvel Rivals

I posted a version of this on r/Overwatch and it turned into a good methodology thread, so I am putting it here too. This is the detailed version of how Counterwatch computes its counter and synergy rates, why counters use binary weighting while synergies use playtime weighting, and what the double-normalization pass is actually for. If you have ever wondered why our numbers look different from a raw-aggregate stats site, this is the answer.

Source data

Counterwatch aggregates roughly 60,000 Overwatch matches a week from opted-in app users. Every match produces hero sessions, one per hero per player, and each session records the hero, the team side, the match outcome, and the match portion, a value from 0 to 1 based on how much of the match that hero was played. The data is processed weekly and sliced by rank division, game type, and stat category (5V5, 6V6, Stadium). Everything below runs on top of those hero sessions.

Counter rate

For each match, every hero on one team is paired with every hero on the other. Each pair counts as 1.0 regardless of playtime, and that is deliberate. It is the single biggest difference between how we compute counters and how a naive aggregate would.

Here is why it matters. When players get countered, they swap, so the countered hero has low playtime in exactly the matches where they lost hardest to a specific opponent. Weight pairs by playtime and you downweight the data that proves the counter works, the matches where someone got hard-countered and swapped out. Binary weighting avoids that trap: a hero who played 20% of a match still contributes a full data point to every enemy matchup, which preserves the signal that the swap was forced.

raw_counter_rate = wins / total_pairs   (for that hero-vs-opponent combo)

Synergy rate

Synergies are the opposite problem. Every hero on a team is paired with every ally, and pairs are weighted by both heroes' match portions multiplied together, portion_a × portion_b. Swaps do not carry the same survivorship bias for synergies, and we want to measure how well two heroes do when both are actually being played together, not how well one does after the other has left. Weighting by the product of their match portions captures the window where both were on the roster at once.

raw_synergy_rate = weighted_wins / weighted_total

Double normalization

Raw rates are useful but biased, in two systematic ways. First, some heroes are just stronger than others: if Hero A wins 55% overall, every counter pair involving Hero A looks inflated on the "A wins against" side and deflated on the "losing to A" side. Second, our user base wins more than 50% of matches overall, because people install an app like this to win more and they do, so without correcting for it every hero looks artificially strong across every matchup.

Two passes strip both effects out. Pass 1, opponent and ally normalization, subtracts each opponent's (or ally's) average win rate across all heroes, which removes the effect of facing or playing alongside a generally strong or weak hero. After it, a positive number means the hero is doing better than average against that specific opponent, controlling for how strong the opponent is overall. Pass 2, hero popularity normalization, subtracts each hero's average already-normalized rate across all their matchups, which removes the bias from our user base winning above 50% globally.

The output of the two passes is a delta, how much better or worse the matchup runs than each hero's overall strength alone predicts. We anchor that delta back into a familiar 50%-centred win-rate space for display, so the numbers on the site stay on the same scale as the tier list. A raw aggregate site might tell you Cassidy wins 48% against Winston, which looks like a losing matchup. After double normalization we show roughly 51%. Once you control for both heroes' overall strength, Cassidy actually does about 3 points better than expected into Winston.

The Cassidy example, worked

Before I added the double normalization, an earlier version of the data showed Cassidy with lower win rates against all the heroes he is supposed to hard-counter. That looked obviously wrong, and it is what kicked the normalization work off. The explanation is simple in hindsight: Cassidy gets played into dive comps, against Tracer, Genji, and Winston, and those dive comps are strong in the current meta, so matches where Cassidy faces them tend to have higher enemy-team win rates overall. Raw aggregation blamed that on Cassidy losing the matchup, when the real signal is that Cassidy does better than expected against those dive heroes, relative to how well the dive team was going to do anyway. Pass 1 strips out how strong the enemy hero is overall. Pass 2 strips out how popular the friendly hero is among our users. What is left is the matchup.

Live match scoring

The tier list and the matchup tables aggregate daily, but in a live match both the overlay and the web team builder combine several stats into a real-time read for each player and the team. Each player's score combines three factors: the counter impact (this hero's average counter win rate against every enemy hero, with enemy scores as the inverse, zero-sum), the synergy score (average synergy win rate with every ally), and the map win rate (how this hero does on the current map). The in-game per-player badge expresses each factor as a delta from 50% and adds them up. A Tracer at +2% counter, +1.5% synergy, +0.7% map reads as +4.2 points.

The team-level win-chance prediction works differently. Instead of summing per-player deltas, the web team builder takes a weighted blend across the whole lineup and clamps it:

winChance = clamp(0.5 × counter + 0.3 × synergy + 0.2 × map, 0.05, 0.95)

Counter carries the most weight because counter-picking is the highest-leverage decision in a match. Synergy matters but less, since a comp that synergises perfectly against the wrong enemy still loses. Map is the smallest weight, because hero-versus-map effects are a weaker signal than hero-versus-hero, and plenty of players do not optimise by map anyway. The clamp keeps the prediction sane at the extremes, since even an unbeatable lineup on paper leaves room for human error and luck.

What this does not handle yet

A couple of limits worth being upfront about. The first is map granularity. 60,000 matches a week is plenty for reliable hero-versus-hero numbers across the roster, but not enough to break those down per map. Pharah versus hitscan on Gibraltar is genuinely a different matchup than on King's Row, and folding maps together dilutes some real situational counters. I handle this partly in live scoring by pulling the map win rate as its own factor, but I do not currently slice the counter and synergy tables by map. Data volume is the gate.

The second is MMR spread inside a rank. Even within one rank tier there is real MMR variance between players, and higher-MMR players at the same tier play around counters and stack synergies more efficiently. For Overwatch I can only read the rank of players with public profiles, so I almost never have a full picture of the MMR gap between the two teams in a match. Marvel Rivals exposes rank more completely. For Overwatch the data simply is not there to weight by MMR spread, so rather than guess, I leave the factor out.

Why it matters

A 48% counter rate on a raw aggregate site does not mean a hero is losing the matchup. It means the matchup reads that way before you correct for how strong the opponent is, the selection bias of the user base, and whatever else is baked into the raw numbers. Counterwatch's numbers are built to be actionable at the draft screen: does picking this hero into that one actually help me win. The double normalization is what makes that answer trustworthy.

For the tier list this feeds, see the Overwatch tier list or Marvel Rivals tier list. For the live surface, the Counterwatch app runs the same logic on the data as it comes in. If you want to argue with any of this, I read every message at oskar@counterwatch.gg and the Discord is open.

Ready for the live version?

Counterwatch runs inside Overwatch and Marvel Rivals. Live counter picks, win chance, and hero swaps without typing or tabbing out.

  • Deeper stats - personal win rates, hero grades, and match history beyond what's shown here
  • Live match data - real-time counter suggestions and win chance as heroes are picked
  • Performance tracking - session grades, hero grades, and trends over time compared to your averages
  • In-game overlay - match roster, hero swaps, and counter picks right on your screen