# Is Scottish football less competitive than the rest of Europe?

Source: https://www.footballdatascience.co.uk/research/competitive-balance-europe
Published: 2026-10-05

> Nine European leagues, 26 seasons, 76,154 matches. Scotland is the only one with two champions, and its gap from second to third is twice anyone else's. Take out Celtic and Rangers, though, and the rest of the league is as even as any of the nine.

**On the terraces:** Everyone says Scottish football is a two-horse race. On the title they're right, and nowhere else in Europe comes close. This piece shows the rest of the league is as tight as anywhere, and that Europe's big leagues have been getting more like Scotland, not the other way round.

## The research question

"It's a two-team league." Anyone who follows Scottish football has heard it, usually from someone comparing it with England. **Is Scottish football really less competitive than other European leagues?**

Competitive can mean three different things, and they don't have to agree:

1. **The title.** How many different clubs win the league?
2. **The table.** How far apart are the teams from top to bottom?
3. **The match.** On a given Saturday, how often does the favourite win?

## The dataset

Every top-division match from 2000/01 to 2025/26 in nine leagues, from the football-data.co.uk files: Scotland, England, Spain, Germany, Italy, France, the Netherlands, Portugal and Turkey. That's **76,154 matches** and 234 league seasons, from Scotland's 5,879 to England's and Spain's 9,880 each. Bet365's prices are there from 2002/03.

Belgium and Greece have files too but are left out. Both now finish the season with championship play-offs, and Belgium halves the points before them, so their tables can't be compared like for like with a league that plays a straight season. Scotland's split stays in: every club still plays 38 games and every point counts.

## The method

- **Champions.** The team top of the table in each season, by points, then goal difference, then goals. That matches the honours list except in four seasons, which the snippet corrects by hand: Spain 2006/07 (Real Madrid, on head-to-head), Italy 2004/05 and 2005/06 (Juventus stripped of both; the first left unawarded, the second given to Inter), and the Netherlands 2019/20 (voided, no champion).
- **The gap at the top.** Points per game between first and second, and between second and third, so seasons of different lengths compare fairly.
- **How spread out the table is, against luck.** Even a league of identical teams would produce a table with a top and a bottom, just from luck. So for every season the snippet replays the real fixture list 200 times with every match decided at random, using that league's own rates of home wins, draws and away wins. The real spread of points per game, divided by that luck-only spread, says how many times more spread out the table is than luck alone would make it. 1 would be a league of equals.
- **Without the giants.** The same measure using only the matches between the other clubs, leaving out each league's two most successful clubs over the 26 seasons.
- **Then and now.** The first eight seasons against the last eight.
- **One match.** The favourite on Bet365's prices (with the margin taken out), its chance, and how often it won.

Every comparison comes with a luck margin of about two standard errors either side.

## Results

### The title: Scotland is out on its own

| League (champions) | Most titles |
|---|---|
| Scotland (2) | Celtic 20 |
| Spain (4) | Barcelona 13 |
| Portugal (4) | Porto 13 |
| Italy (5) | Juventus 11 |
| Netherlands (5) | PSV 12 |
| England (6) | Man City 8 |
| Germany (6) | Bayern 19 |
| Turkey (6) | Galatasaray 12 |
| France (8) | PSG 12 |

**Celtic and Rangers won all 26 titles.** No other league had fewer than four different champions. Spain's big two won 22, as did Germany's, where Bayern won 19 on their own, one fewer than Celtic. The difference is that Germany also had a Dortmund, a Werder Bremen, a Stuttgart, a Wolfsburg and a Leverkusen who each broke through at least once. In Scotland nobody else has.

The gap at the top shows the shape of it. In an average Scottish season the champions finish **0.31 points a game** ahead of second, about 12 points over 38 games. That's high, but Germany (0.29) and France (0.25) are close. The real outlier is the next gap: second finish **0.45 points a game** ahead of third (± 0.11), about 17 points. No other league is above 0.24. Germany and France are one-club leagues at the top; Scotland is the only two-club league, with a gulf below both.

### The whole table: spread out, but the giants do the spreading

<figure class="rank-chart">
<div role="img" aria-label="How spread out each league's table is, as a multiple of luck. Whole league: Scotland 2.30, Portugal 2.25, Netherlands 2.20, Italy 2.18, England 2.17, Spain 2.01, Turkey 1.96, Germany 1.90, France 1.83. Without each league's two giants: Scotland 1.49, Portugal 1.76, Netherlands 1.77, Italy 1.92, England 1.94, Spain 1.58, Turkey 1.62, Germany 1.57, France 1.61.">

</div>
<figcaption>How spread out each table is, as a multiple of what luck alone gives a league of equal teams, with a luck margin. Gold is the whole league; mint leaves out the matches involving its two most successful clubs.</figcaption>
</figure>

Scotland's table is the most spread out of the nine, **2.30 times** what luck alone would give (± 0.11). But it isn't far ahead: Portugal (2.25), the Netherlands (2.20), Italy (2.18) and England (2.17) are all within the luck margins. As a whole league, Scotland is one of a group of five, not a freak.

Now take out the two giants and look only at the matches between everyone else. Every league comes down, and Scotland comes down furthest, to **1.49** (± 0.12). That's as even as any of the nine: level with Germany (1.57) and Spain (1.58) within luck, and far more even than England (1.94) or Italy (1.92) without their big two. **Scottish football is two clubs and then one of the most open leagues in Europe.** It's why the fight for third, the top six and survival is so often close even when the title isn't.

There's a natural experiment in the middle of the data, too (see [do football stats mislead?](/research/correlation-causation) for why those matter). After the financial collapse of 2012, Rangers restarted in the fourth tier and were out of the top flight from 2012/13 to 2015/16. With one giant instead of two, Scotland's table averaged **1.91** times luck against **2.38** in the other 22 seasons, and 2012/13 was the most even Scottish season of all at 1.55. Remove one giant and the league closes up at once.

### Then and now: Europe has been catching up

<figure class="rank-chart">
<div role="img" aria-label="Spread of the table, first eight seasons to last eight. Scotland 2.44 to 2.38, Netherlands 2.18 to 2.23, Turkey 1.94 to 2.02, England 2.09 to 2.26, Italy 2.10 to 2.32, Germany 1.72 to 2.08, Portugal 2.01 to 2.41, Spain 1.65 to 2.09, France 1.55 to 2.01.">

</div>
<figcaption>How spread out each table was in 2000/01 to 2007/08 (hollow) and in 2018/19 to 2025/26 (filled), as a multiple of luck. Scotland in gold.</figcaption>
</figure>

Scotland's spread hasn't changed: **2.44** in the first eight seasons, **2.38** in the last eight. Most of the others have moved the other way. France went from 1.55 to 2.01, Spain from 1.65 to 2.09, Portugal from 2.01 to 2.41, Germany from 1.72 to 2.08 and Italy from 2.10 to 2.32, each by more than its luck margin. England rose by less than its margin, and the Netherlands and Turkey barely moved.

In the early 2000s Scotland's table was the most spread out of the nine by a clear distance. In the last eight seasons Portugal's (2.41) is level with it and Italy's (2.32) close behind. Scotland hasn't become less competitive; much of Europe has become more like Scotland.

### One match: about as predictable as Spain

| League | Expected | Won |
|---|---|---|
| Netherlands | 54.8% | 56.2% |
| Portugal | 52.4% | 55.0% |
| England | 53.1% | 54.3% |
| Italy | 51.9% | 54.2% |
| Scotland | 52.7% | 53.6% |
| Spain | 51.8% | 53.6% |
| Turkey | 50.4% | 52.8% |
| Germany | 51.6% | 51.8% |
| France | 49.4% | 50.9% |

The favourite's chance on Bet365's prices and how often it won, 2002/03 to 2025/26; the luck margins are about 1 to 1.4 points either way.

Match by match, Scotland sits in the middle. Favourites won **53.6%** of Scottish matches (± 1.4), the same as in Spain. Only in the Netherlands (56.2%) did they clearly win more often; Portugal, England and Italy are within luck of Scotland. A random Saturday in the Scottish Premiership is no more of a foregone conclusion than one in La Liga, because most of those Saturdays don't involve Celtic or Rangers playing at home to a side from the bottom half. In every league the favourite won a little more often than the prices said (only just, in Germany), the [favourite–longshot bias](/research/bookmaker-odds) showing up again.

## Limitations

- **League size.** Scotland has 12 clubs; most of the others have 18 or 20. Two giants are a sixth of the Scottish league and a tenth of the English one, so they pull the Scottish spread further. That's the structure of the league rather than a flaw in the measure, and the "without the giants" figures take it out.
- **Who the giants are.** "The two most successful clubs" is decided from the 26 seasons' titles, so it's the same two clubs throughout. In a league where the big two changed (England's are Manchester City and Manchester United), that's a rougher cut.
- **Tables, not money.** The data has results and odds, not budgets or wages. It can say how uneven the leagues are, not why.
- **Champions from the table.** Points deductions aren't in the data, so a deduction can move a club a place or two. The champions were checked against each league's honours list, which is how the four seasons corrected by hand were found.
- **Different seasons, different lengths.** 2019/20 was cut short in Scotland, France and the Netherlands. Every measure uses points per game, so shorter seasons count, they just carry more luck.

## Conclusion

On the title, the terraces are right: Scotland is the least competitive league of the nine by a distance, with two champions in 26 seasons and a 17-point gulf, on average, between second and third. On everything else, it isn't. Leave out Celtic and Rangers and the rest of the league is as even as any in Europe, a random match is no easier to call than in Spain, and Scotland's table hasn't become more lopsided in 26 seasons while several of the big leagues' tables have. The data points to a two-club problem, not a league problem.

For how much of a single season's table is luck in the first place, see [does the league table never lie?](/myth-or-maths/league-table-never-lies), and for how early the title is settled, [when is the title decided?](/research/title-race).

## Reproduce the analysis

The results files are published by [football-data.co.uk](https://www.football-data.co.uk/data.php). Get the Scottish files with the [download script](/data#get-the-files), adding the other leagues' codes: `python get_football_data.py E0 SP1 D1 I1 F1 N1 P1 T1`. They aren't rehosted on this site. The snippet takes about ten seconds, most of it replaying fixtures at random:

```python
# From Football Data Science by Bryan McGuire. Free to use with credit.
# https://www.footballdatascience.co.uk/research/competitive-balance-europe
import csv
import random
from collections import Counter, defaultdict
from statistics import mean, pstdev, stdev

LEAGUES = {"SC0": "Scotland", "E0": "England", "SP1": "Spain", "D1": "Germany", "I1": "Italy",
           "F1": "France", "N1": "Netherlands", "P1": "Portugal", "T1": "Turkey"}
SEASONS = [f"{y % 100:02d}{(y + 1) % 100:02d}" for y in range(2000, 2026)]
# where the honours list differs from the table: head-to-head (Spain), titles stripped (Italy), a voided season
OFFICIAL = {("SP1", "0607"): "Real Madrid", ("I1", "0405"): None, ("I1", "0506"): "Inter", ("N1", "1920"): None}
rnd = random.Random(2026)  # fixed, so every number repeats exactly

def load(div, season):
    matches = []
    with open(f"{div}_{season}.csv", encoding="latin-1") as f:
        for r in csv.DictReader(f):
            r = {k.lstrip("﻿ï»¿"): v for k, v in r.items() if k}
            home = (r.get("HomeTeam") or r.get("HT") or "").strip()  # older files say HT/AT; some names end in a space
            away = (r.get("AwayTeam") or r.get("AT") or "").strip()
            if home and away and r.get("FTR") in ("H", "D", "A"):
                try:
                    inv = [1 / float(r["B365" + x]) for x in "HDA"]
                    probs = [p / sum(inv) for p in inv]
                except (KeyError, ValueError):
                    probs = None
                matches.append((home, away, int(r["FTHG"]), int(r["FTAG"]), r["FTR"], probs))
    return matches

def table(matches):  # points, goal difference, goals, games
    t = defaultdict(lambda: [0, 0, 0, 0])
    for home, away, hg, ag, _, _ in matches:
        for team, gf, ga in ((home, hg, ag), (away, ag, hg)):
            t[team][0] += 3 if gf > ga else 1 if gf == ga else 0
            t[team][1] += gf - ga
            t[team][2] += gf
            t[team][3] += 1
    return sorted(t.items(), key=lambda kv: (-kv[1][0], -kv[1][1], -kv[1][2]))

def spread(matches, runs=200):
    """How spread out the table is (points per game), as a multiple of what luck alone gives
    a league of equal teams playing the same fixtures with the same home, draw and away rates."""
    real = pstdev([v[0] / v[3] for _, v in table(matches)])
    n = len(matches)
    results = Counter(m[4] for m in matches)
    home_win, draw = results["H"] / n, results["D"] / n
    luck = []
    for _ in range(runs):
        pts, games = defaultdict(int), defaultdict(int)
        for home, away, *_ in matches:
            games[home] += 1
            games[away] += 1
            u = rnd.random()
            if u < home_win:
                pts[home] += 3
            elif u < home_win + draw:
                pts[home] += 1
                pts[away] += 1
            else:
                pts[away] += 3
        luck.append(pstdev([pts[k] / games[k] for k in games]))
    return real / mean(luck)

def margin(values):  # about 2 standard errors either side of the average
    return 2 * stdev(values) / len(values) ** 0.5

rows = {}
for div, name in LEAGUES.items():
    seasons = {s: load(div, s) for s in SEASONS}
    champions = []
    for s, m in seasons.items():
        top = table(m)[0][0]
        champions.append(OFFICIAL.get((div, s), top))
    titles = Counter(c for c in champions if c)
    giants = [club for club, _ in titles.most_common(2)]  # the two clubs with most titles
    spreads = [spread(m) for m in seasons.values()]
    rest = [spread([x for x in m if x[0] not in giants and x[1] not in giants]) for m in seasons.values()]
    ppg = [[v[0] / v[3] for _, v in table(m)] for m in seasons.values()]
    priced = [x for m in seasons.values() for x in m if x[5]]
    fav_won = mean(x[4] == "HDA"[x[5].index(max(x[5]))] for x in priced)
    rows[name] = dict(matches=sum(len(m) for m in seasons.values()), champions=len(titles), giants=giants, top2=titles[giants[0]] + titles[giants[1]], most=titles.most_common(1)[0],
                      spread=mean(spreads), spread_m=margin(spreads), early=mean(spreads[:8]), late=mean(spreads[-8:]),
                      change_m=(stdev(spreads[:8]) ** 2 / 8 + stdev(spreads[-8:]) ** 2 / 8) ** 0.5 * 2,
                      rest=mean(rest), rest_m=margin(rest), by_season=spreads,
                      gap12=mean(p[0] - p[1] for p in ppg), gap23=mean(p[1] - p[2] for p in ppg), gap23_m=margin([p[1] - p[2] for p in ppg]),
                      fav_p=mean(max(x[5]) for x in priced), fav_won=fav_won, priced=len(priced))

print(f"{sum(r['matches'] for r in rows.values())} matches in {len(rows)} leagues: " + ", ".join(f"{n} {r['matches']}" for n, r in rows.items()))
print("1. The title: different champions in 26 seasons, and the two biggest clubs' share")
for name, r in sorted(rows.items(), key=lambda kv: kv[1]["champions"]):
    print(f"   {name:12} {r['champions']} champions; {r['giants'][0]} and {r['giants'][1]} won {r['top2']}; most: {r['most'][0]} {r['most'][1]}")
print("2. The gap at the top, points per game: 1st to 2nd, 2nd to 3rd")
for name, r in sorted(rows.items(), key=lambda kv: -kv[1]["gap23"]):
    print(f"   {name:12} {r['gap12']:.2f}  {r['gap23']:.2f} ± {r['gap23_m']:.2f}")
print("3. The whole table: spread as a multiple of luck (1 = all luck), with a luck margin")
for name, r in sorted(rows.items(), key=lambda kv: -kv[1]["spread"]):
    print(f"   {name:12} {r['spread']:.2f} ± {r['spread_m']:.2f}; without its two giants {r['rest']:.2f} ± {r['rest_m']:.2f}")
print("4. Then and now: first eight seasons v last eight")
for name, r in sorted(rows.items(), key=lambda kv: kv[1]["late"] - kv[1]["early"]):
    print(f"   {name:12} {r['early']:.2f} -> {r['late']:.2f} ({r['late'] - r['early']:+.2f} ± {r['change_m']:.2f})")
sc = rows["Scotland"]["by_season"]
print("   Scotland by season: " + ", ".join(f"{s[:2]}/{s[2:]} {v:.2f}" for s, v in zip(SEASONS, sc)))
print(f"   Scotland without Rangers (2012/13 to 2015/16) {mean(sc[12:16]):.2f}, other seasons {mean(sc[:12] + sc[16:]):.2f}")
print("5. One match: the favourite's chance on Bet365's prices, and how often it won")
for name, r in sorted(rows.items(), key=lambda kv: -kv[1]["fav_won"]):
    m = 2 * (r["fav_won"] * (1 - r["fav_won"]) / r["priced"]) ** 0.5
    print(f"   {name:12} {r['fav_p']:.1%} expected, {r['fav_won']:.1%} won ± {m:.1%} ({r['priced']} matches)")
```

## Further reading

- Joseph Buchdahl, [How unpredictable was the 2015/16 Premier League season?](https://football-data.co.uk/blog/premiership_unpredictable.php) (football-data.co.uk). Measures how predictable a season was from the betting odds, with Brier scores, the year Leicester won the league.
- [Old Firm](https://en.wikipedia.org/wiki/Old_Firm), Wikipedia. The history of Celtic and Rangers' rivalry and their share of Scottish honours.
- [Notes for football data](https://www.football-data.co.uk/notes.txt), football-data.co.uk. What each column in the files means.
