Morocco have never played Mexico. Spain have never played Senegal. Japan have never played Portugal. Dozens of pairs of top-40 national teams have never met at all.
Fewer than a third of the 22,155 possible men's international fixtures have been played, and fewer than one in six of the women's. Every pairing is one cell of this grid.
▶ Live: https://dgoodenough.github.io/fifagami/
Every current FIFA member is a row and a column. Each cell is a pairing. Red means those two national teams have never played each other, in 150 years of trying.
Rows and columns are national teams, grouped by confederation and ordered by FIFA ranking within each one. Teams mostly play their neighbours, so the grid fills in along the diagonal and empties out between the blocks.
Red is a pairing that has never happened. Grey is one that has, darker for more meetings. Turn off Never-played and the grid colours by meetings instead, a green ramp from a single match up to Argentina against Uruguay, the most-played fixture in the game, first contested in 1902.
The diagonal is blacked out. A team cannot play itself.
Every view obeys the same controls. The confederation filter, the ticked teams, the dataset toggle and the timeline scrubber narrow all five. Filter to CONMEBOL, scrub to 1950, open One-offs, and you have the South American pairs that had met exactly once by 1950.
| View | What it shows |
|---|---|
| Grid | The 211×211 matrix. |
| Fixtures | Pairings that have never met and have a date on the calendar, with a countdown. Also published as a feed. |
| One-offs | The men's pairings played exactly once, oldest first. Egypt beat Lithuania 10–0 in 1924 and the two have not met since. |
| Near misses | Pairs that have never met, ranked by opponents they already share. Canada and Sweden have more than seventy in common and have never played. |
| Connect | Tonga has played a dozen countries at most, and still reaches every other national team on earth within three matches. |
- Zoom and pan the full grid, on a mouse or a phone. Tap a cell to aim, tap again to open it.
- Men's, women's, or both. Two separate archives with their own FIFA rankings. Both overlays them: met in both games, men's only, women's only, neither. Roughly 4,000 pairings have been played by men and not women; a few hundred the other way.
- Click any cell for every meeting between those two teams, with scores, tournaments and the head-to-head record.
- Timeline scrubber. Drag through the years and watch the grid fill in. Press ▶ and it plays 1872 to today.
- Tick one team for its full record, ranked most- to least-played. Tick several to build a sub-grid.
- Upcoming first meetings highlighted in yellow, from scheduled fixtures up to two years out.
- Sort by confederation then rank, by global rank, by total matches played, or alphabetically.
- Include defunct teams, for Yugoslavia, Czechoslovakia, East Germany, the Saar, South Vietnam and South Yemen.
- Three kinds of empty, told apart. Never met, had not happened yet at the scrubbed year, and never played anyone at all. Brunei, Montserrat, Oman, San Marino, Somalia and Yemen have never played a women's international.
- Keyboard and screen-reader access. Arrow keys walk the cells, Enter opens a head-to-head, and a live region reads out whatever the focus lands on.
- Everything is in the URL, so any view can be linked, bookmarked or captioned. Copy link puts the current one on the clipboard.
- Light and dark, following the OS preference.
| Dataset | Source |
|---|---|
| Men's internationals, 1872–present, ~49k matches | martj42/international_results |
| Women's internationals, 1969–present, ~12k matches | martj42/womens-international-results |
| Historical team renames | former_names.csv (martj42) |
| Current FIFA ranking, men's and women's | FotMob, mirroring the official ranking |
| Scheduled internationals | ESPN public scoreboard API |
| Confederation membership | cnc8/fifa-world-ranking |
| FIFA code to ISO 3166-1 | data/iso2.csv |
Current FIFA members only, 211 teams, unless you switch on defunct sides.
Names are reconciled across three sources. They disagree: Côte d'Ivoire against
Ivory Coast, Korea Republic against South Korea, FotMob's USA and Turkiye and
Czechia. build.py resolves them, and former_names.csv folds historical names into the
modern team, so Zaïre counts as DR Congo and Upper Volta as Burkina Faso. USSR resolves to
Russia and Serbia & Montenegro to Serbia, following the source's lineage. Teams that are
neither a current member nor a curated defunct side, mostly non-FIFA territories like
Martinique and Jersey, are excluded, and the build prints all of them.
Rankings are current and per-gender, pulled at build time. The publication date is recorded
in members.json and shown in the app footer. About 14 members have never been given a women's
ranking and sort last within their confederation.
The archives have a cut-off. martj42 updates within a day or two of most internationals.
The current cut-off for each is in members.json as data_through, and in the app footer.
What counts as "upcoming" is decided by the browser's date, not the build's, so a fixture that has since been played stops being advertised as a first meeting between data refreshes.
build.py uses only the standard library.
python build.py # download, reconcile, aggregate, write docs/data/*.json
python build.py --refresh # force re-download of every source
python build.py --derive # regenerate derived artifacts only, no networkIt writes the artifacts the site loads:
members.json— ordered members with confederation, ranks, flag, data vintagematrix_men.json,matrix_women.json— sparse[i, j, meetings, firstYear, lastYear]matches_men.json,matches_women.json— per-pair detail, fetched only when a cell is clickedyears_men.json,years_women.json— meeting years per pair, delta-encoded, for the scrubberdefunct.json— the defunct-teams layerupcoming.json— scheduled first-ever meetingsfacts.json— the lines the app shows under the headline, derived so they cannot go stalefeed.json,feed.xml— upcoming first meetings, as a subscribable feed
python render_hero.py draws the README image, the share card and the touch icon. It reads the
palette out of docs/style.css, so the pictures cannot drift from the site. The share card
quotes a live figure, so the daily refresh regenerates it.
Then serve it:
python -m http.server -d docs 8000python -m unittest discover -s tests -vA daily GitHub Action commits the build straight to the repo, so nobody reads that diff. The
suite runs in CI on every push and again inside the refresh workflow before it is allowed to
commit. build.py raises on a structurally broken artifact. The tests catch the subtler drift:
a confederation losing members, the most-played fixture changing identity, the feeds falling
out of sync, a fact on the landing page that stopped being true when two teams finally met.
Upcoming first meetings are published as RSS and JSON Feed. Item ids are stable per pair and date, so a reader shows an entry once: when a fixture between two nations that have never met first appears on the calendar. Every item links back to that pairing in the app.
A static site. Vanilla JavaScript and a canvas, no build step, no runtime dependencies, hosted
on GitHub Pages from docs/. The 44,000-cell grid is drawn with view-culling for smooth
zoom and pan, and Pointer Events give mouse and touch a single interaction path.
Two things keep it quick at 22,155 pairings. The never-played count is a prefix sum over each pair's first-meeting year, so a timeline drag reads it in constant time per frame instead of walking every pair. The played graph is held as bitsets, so common-opponent counts for every never-played pair come out in a few milliseconds.
The one third-party script is GoatCounter, which counts pageviews without cookies or cross-site identifiers.
Styled with Ledger. Greyscale carries structure, colour carries meaning: red is never, green is played, yellow is scheduled.
- Cluster analysis. The British Isles light up as a dense little block. Community detection over the played graph could surface clusters like that automatically.
- The loneliest nodes. Ranking teams by how few distinct opponents they have ever faced.
Match data © the martj42 datasets. Rankings via FotMob. Upcoming fixtures via ESPN. Confederation mapping from cnc8/fifa-world-ranking. Concept inspired by Scorigami (Jon Bois). Code under the MIT License.
