# Display Football Standings Without Mixing Groups

Standings are more than points sorted in descending order. A competition can contain groups, stages, deductions and tiebreak rules that a generic sorter does not know. When displaying the season standings response, preserve the supplied position and group instead of rebuilding the ranking from points and goal difference.

By Stats API Editorial (AI-assisted) · Reviewed by Codex automated editorial review · Published 2026-10-23 · Last reviewed 2026-10-11

Canonical URL: https://stats-api.com/blog/football-standings-groups-official-position

## Preserve the table supplied by the endpoint

Standings are more than points sorted in descending order. A competition can contain groups, stages, deductions and tiebreak rules that a generic sorter does not know. When displaying the season standings response, preserve the supplied position and group instead of rebuilding the ranking from points and goal difference.

Use the competition and season IDs resolved through discovery. The team standings operation can contain rows from different contexts, so a single team response should not be flattened into one table without checking competition, season and group. The same team can legitimately appear in more than one context.

## Partition before rendering

Group rows by competition ID, season ID and group. Within each partition, order by the supplied position. A null group is a valid ungrouped context; it is not a wildcard that should merge every named group. Use a stable internal representation and choose a readable display label only at rendering time.

The example creates two groups whose leaders both have position one. Sorting all rows together would interleave those leaders and imply one overall ranking. Partitioning keeps each group's order independent and makes repeated positions across different groups expected rather than erroneous.

## Run the reproducible example

This Python 3 example uses deliberately synthetic inputs, not a downloaded match or a forecast for a real fixture. Save it as example.py and run python3 example.py. It uses only the standard library and its assertions check the behavior discussed here. Replace the fixture inputs with validated API records when connecting your application; demo identifiers are not production lookup values.

**Complete offline example**

```python
from collections import defaultdict

def tables(rows):
    grouped = defaultdict(list)
    for row in rows:
        if row["goal_difference"] != row["goals_for"]-row["goals_against"]:
            raise ValueError("goal-difference mismatch")
        key = (row["competition"]["id"],row["season"]["id"],row["group"])
        grouped[key].append(row)
    return {key:sorted(values,key=lambda x:x["position"]) for key,values in grouped.items()}

def row(team, group, position, points):
    return {"competition":{"id":"comp_demo"},"season":{"id":"season_demo"},
            "group":group,"position":position,"team":{"id":team},"points":points,
            "goals_for":5,"goals_against":2,"goal_difference":3}

result = tables([row("team_b","A",2,6),row("team_a","A",1,6),row("team_c","B",1,4)])
assert len(result) == 2
assert [r["team"]["id"] for r in result[("comp_demo","season_demo","A")]] == ["team_a","team_b"]
assert result[("comp_demo","season_demo","B")][0]["points"] == 4
print({str(k):[r["team"]["id"] for r in v] for k,v in result.items()})
```

## Read the result

The output contains two separate groups. Group A retains the supplied order A then B despite equal points, and group B has its own first-place team. No homemade tiebreak or points adjustment is applied.

## Check arithmetic without overwriting facts

For each row, played should equal won plus drawn plus lost and goal_difference should equal goals_for minus goals_against under the usual row meaning. These checks are useful diagnostics. Do not automatically replace the supplied points with three times wins plus draws: a deduction or competition rule can make that calculation differ legitimately.

If a row fails a structural or arithmetic check, retain the response for review and render an appropriate unavailable state. An application can flag inconsistent data without claiming to know the correct replacement. The code here checks the goal-difference identity on its synthetic projected rows and leaves points untouched.

## Keep table identity in links and caches

A table's competition, season and group must travel with any exported image, cached payload or deep link. A bare team ID does not identify the standings context. If users switch seasons, clear or isolate the previous response while the new request loads so yesterday's table is not labelled with today's selector.

Test ungrouped leagues, multiple groups, equal points, deductions and an empty response. Preserve descriptions and form strings as returned text where useful, but do not infer promotion or relegation rules from a hard-coded color palette. The interface should explain the actual data it has rather than silently adding competition rules.

## Connect this to your project

Choose the standings endpoint when your application wants the supplied table, and the league table calculator when it intentionally teaches result-based aggregation. Keep those two purposes explicit in the interface and use the same competition and season selection throughout.

- [Try the educational table generator](https://stats-api.com/tools/league-table-generator)
- [Read the API contract](https://stats-api.com/docs)
- [Explore the football API](https://stats-api.com/football-api)
- [Review stable ID practices](https://stats-api.com/blog/stop-football-ai-inventing-team-ids)
