Artificial Wasteland · 50 days of the Swiss rail record
Where the Lateness Goes
A timetable is not a statement about how fast a train can go. It is that plus a supplement, spread along the route so a train that falls behind can catch up. The railway literature sets norms for how much of it there should be and argues about where to put it. Nobody has published how much of it a national network actually gives back. Here is 6,485,058 measured legs of one, and the answer has a ceiling in it.
Leaving 5 min late, the median train arrives 2 min 45 s late. The timetable gave back 2 min 15 s, which is 45% of it. 2,898 journeys.
That is the whole finding, and the slider is the argument. Move it to one minute and the lateness is essentially gone by the destination. Move it to fifteen and almost none of it is. The Swiss timetable is a filter: it removes small lateness completely and passes large lateness through nearly untouched.
What was measured, and on what
Switzerland publishes, every day, a file with one row per train per stop carrying both the scheduled time and the realised time. This reads the publisher's whole rolling window as it stood on 2026-09-13: 2026-07-25 to 2026-09-12, 50 days, 8,902,734 rail rows from 53 operators, 737,701 usable train-days and 6,485,058 station-to-station legs where both ends carry a measured time rather than a forecast.
The publisher is explicit that most of what is in the file is not a measurement, and the distinction is a field. Its own cookbook gives the enumeration:
UNBEKANNT (no forecast or actual times for this and all preceding stops) · PROGNOSE (arrival forecast) · GESCHAETZT (calculated actual arrival time) · REAL (effective actual arrival time)
Nothing here uses a value whose status is not REAL. That is also why this page does not simply count minutes late: a delay figure built from forecasts would be measuring the forecaster.
The number a passenger wants
Take every train that left its first station d whole minutes late and ask how late it was when it reached its last. No model, no estimate of how fast the train could have gone, no parameter to choose: a difference of two published timestamps, twice.
| left this late (min) | journeys | median arrival delay | lateness kept |
|---|---|---|---|
| 0 | 440,584 | -6 s | - |
| 1 | 85,004 | 10 s | 17% |
| 2 | 27,925 | 33 s | 28% |
| 3 | 11,206 | 60 s | 33% |
| 4 | 5,098 | 99 s | 41% |
| 5 | 2,898 | 165 s | 55% |
| 6 | 1,518 | 234 s | 65% |
| 7 | 914 | 299.5 s | 71% |
| 8 | 698 | 381 s | 79% |
| 9 | 457 | 450 s | 83% |
| 10 | 442 | 466.5 s | 78% |
| 11 | 238 | 536 s | 81% |
| 12 | 194 | 622 s | 86% |
| 13 | 170 | 679.5 s | 87% |
| 14 | 104 | 759 s | 90% |
| 15 | 151 | 877 s | 97% |
A train one minute late arrives 10 seconds late: 83% of the lateness is gone. Two minutes late becomes 33 seconds. Five minutes late becomes 2 min 45 s, so barely half of it is recovered. Ten minutes late becomes 7 min 47 s, and fifteen minutes late becomes 14 min 37 s, which is 97% of what it started with.
Why it has a ceiling
The mechanism is visible one leg at a time. For every station-to-station leg, bin the trains by how late they left the first station and take the median change in lateness across the leg. That is how much the leg gave back.
A leg gives back 19 seconds to a train that is barely late, rises to 48 seconds at 3 minutes, and then falls away. It never exceeds about 48 seconds however late the train is. The median leg class gives back 39 seconds at its best, and 1,260 of 5,824 leg classes give back nothing at all.
So the arithmetic is forced. Recovery is bounded per leg, which means the lateness a journey can undo is bounded by how many legs it has, not by how much help it needs. A train ten minutes down would need something like fifteen consecutive maximally generous legs, and journeys do not have them. Past roughly eight minutes the railway stops being able to help and simply carries the lateness to the destination.
| entered this late (min) | traversals | seconds given back |
|---|---|---|
| 0 | 3,191,091 | 19 s |
| 1 | 1,865,901 | 41 s |
| 2 | 725,071 | 47 s |
| 3 | 281,631 | 48 s |
| 4 | 125,677 | 47 s |
| 5 | 68,901 | 41 s |
| 6 | 35,712 | 44 s |
| 7 | 21,624 | 42 s |
| 8 | 14,409 | 40 s |
| 9 | 9,869 | 38 s |
| 10 | 9,636 | 23 s |
| 11 | 5,538 | 40 s |
| 12 | 4,210 | 39 s |
| 13 | 3,202 | 40 s |
| 14 | 2,660 | 35 s |
| 15 | 3,104 | 14 s |
Where the slack is kept
Two places, and both are measurable separately. Standing, not running. Of the scheduled time a train spends stationary at a platform, 60% is above the first percentile of what it is observed to need; of scheduled running time, 20% is. Dwell is where a timetable hides its money.
In front of the junction. Switzerland runs a clock-face timetable, in which trains must reach the interchange nodes just before the hour and the half hour so that connections can be made. That should push slack into the last leg before a hub, and it does: the mean best absorption of a leg ending at Zürich HB, Bern, Basel SBB, Olten, Luzern, Lausanne or Genève is 87.0 seconds against 45.4 seconds everywhere else.
What it costs
Slack is not free: it is journey time that the passenger spends and the train does not need. Comparing a route's scheduled running and dwell time against the first percentile of what those same legs are observed to take gives a lower bound on the share of the clock that is supplement rather than travel.
| line | route | scheduled | legs covered | slack share |
|---|---|---|---|---|
| RE5 | Solothurn to Bern | 35 min | 9/9 | 26.6% |
| RE5 | Bern to Solothurn | 35 min | 5/5 | 19.2% |
| IR37 | Zürich HB to Basel SBB | 69 min | 7/7 | 22.3% |
| IR36 | Basel SBB to Zürich HB | 77 min | 7/7 | 20.9% |
| IR70 | Luzern to Zürich HB | 42 min | 9/9 | 20.8% |
| RE13 | Landquart to Klosters Platz | 39 min | 4/4 | 21.8% |
| RE13 | Klosters Platz to Landquart | 40 min | 7/7 | 28.4% |
| RE7 | Chur to Ilanz | 34 min | 9/9 | 33.6% |
| RE7 | Ilanz to Chur | 35 min | 3/3 | 24.5% |
| IR70 | Zürich HB to Luzern | 41 min | 5/5 | 18.1% |
| RE1 | Davos Platz to Klosters Platz | 28 min | 3/3 | 19.8% |
| IR90 | Brig to Genève-Aéroport | 160 min | 13/13 | 21.1% |
| IR90 | Genève-Aéroport to Brig | 157 min | 14/14 | 20.3% |
| IR17 | Bern to Olten | 47 min | 4/4 | 15.9% |
| RE24 | Luzern to Olten | 47 min | 15/15 | 17.9% |
| RE24 | Olten to Luzern | 49 min | 10/10 | 20.6% |
Long-distance categories only, by trip count. The forty-route table the pre-registration fixed is in the payload below and is dominated by suburban shuttles; this is a display choice and changes no number in it.
Seven predictions, written down first
The estimators, the exclusions and seven falsifiable predictions were committed before analyse.mjs existed, in research/where-the-lateness-goes/PREREGISTRATION.md. The verifier asks git for both commit times and fails if they are the wrong way round. 4 held and 3 broke.
| # | registered before the analysis | what happened | |
|---|---|---|---|
| P1 | Most of the railway absorbs nothing: the median class gives back under 60 seconds. | HELD | median K = 39 s over 5,807 segment classes |
| P2 | Absorption is concentrated: the top tenth of segments holds more than half of it. | BROKE | the top decile holds 33.4%, not half; 911 classes have K below zero |
| P3 | The absorption curve rises to five minutes of lateness, then flattens. | BROKE | it rises to a peak of 48 s at 3 minutes and then falls away |
| P4 | Standing absorbs more than running, per second of scheduled time. | HELD | 60.0% of scheduled dwell is above the floor against 20.4% of scheduled running time |
| P5 | Slack present exceeds slack used, in more than half of segments. | HELD | 91.7% of 5,807 classes |
| P6 | The clock-face timetable puts the slack in front of the hub. | HELD | mean K into the seven hubs 87.0 s against 45.4 s elsewhere |
| P7 | Slack does not buy punctuality in the cross-section. | BROKE | Spearman 0.193, Pearson 0.167, over 5,807 classes: positive, not zero |
The interesting break is the last one. The prediction was that slack would not buy punctuality across segments, because planners put slack where trains are already unreliable, which should cancel or reverse the association. It does not: the correlation is positive (Spearman 0.193). That is not evidence that adding slack would improve punctuality, and this page does not claim it is. It is observational, the allocation is not random, and a positive sign under a confound that should have pushed the other way is a fact that wants an experiment nobody can run from outside.
The crosscheck, and where it lands
SBB's own 2025 annual report gives train punctuality as 94.1 per cent, defining a train as punctual when it arrives with less than three minutes' delay and counting cancelled trains as unpunctual. The Federal Office of Transport, over subsidised regional traffic for the same year, gives 95.37 per cent of train runs. Computed here from the open file, over every rail operator and every stop, dropping cancellations, the same threshold gives 93.32%; counting every cancelled stop that had a scheduled arrival as unpunctual instead gives 90.36%.
The bracket contains neither published figure exactly and it should not: SBB reports its own trains, the Federal Office reports regional lines at designated measuring points, and this counts all 53 operators at every stop. The point of the comparison is that an independent route through public data lands two or three points from the official number rather than somewhere else entirely, which is the most this crosscheck can honestly buy.
The legs that cannot have happened
Asking how long a train took between two stops is a question with no negative answers, so any negative answer is a defect. There are 24,337 of them among 6,485,058 legs, concentrated in 193 leg classes, and 11 of the largest 25 share one cause: the departure recorded at the first stop is really the departure from the second. In 8 of them the two stops carry the same departure timestamp, to the second, in most of their pairs.
| leg | cat | median run | arrival chain | departure chain | identical departure stamp |
|---|---|---|---|---|---|
| Diessenhofen to St. Katharinental | S | -151 s | 42 s | -20 s | 1% |
| Seon Nord to Seon | S | -108 s | 63 s | -13 s | 0% |
| Bellach to Solothurn Allmend | S | -183 s | -41 s | -22 s | 0% |
| St. Katharinental to Diessenhofen | S | -19 s | 166 s | -30 s | 0% |
| Le Pâquier-Montbarry to La Tour-de-Trême Ronclina | S | -71 s | 117 s | -46 s | 1% |
| La Tour-de-Trême Ronclina to Le Pâquier-Montbarry | S | -107 s | 130 s | -89 s | 0% |
| Igis to Landquart Ried | S | -93 s | 1 s | 0 s | 100% |
| Landquart Ried to Igis | S | -42 s | 60 s | 0 s | 100% |
| Lessoc to Albeuve | S | -275 s | 74 s | 0 s | 100% |
| Albeuve to Neirivue | S | -240 s | 35 s | -112 s | 1% |
These are small halts on regional lines. Nothing here says a train was mis-run; it says the record cannot state when it left the first of two adjacent stops, because it reuses one event for both. Those traversals are dropped from every estimator on this page, and the count is published rather than swept up.
The check
Every number above is recomputed in your browser from an embedded copy of out/results.json, the same file the verifier reads, and the script overwrites the text this page shipped with. If your browser runs JavaScript, what you are reading was computed here, now. (not yet recomputed)
The chain, end to end: fetch.mjs downloads the publisher's daily files and keeps the rail rows, recording each day's URL, byte length and sha256; analyse.mjs reads them and writes out/results.json; impossible.mjs diagnoses the negative legs; build-page.mjs writes this file. Nothing is transcribed by hand at any step.
And the rows are yours. The 5,824 leg classes behind every figure here are published as data at /data/timetable-recovery/, one JSON object per line, with a JSON Schema, the licence position of each source written out, and a dependency-free validate.mjs that re-checks every row and the digests without importing anything of ours. It is in the Data Room at 7 of 7 conditions. Ask it a question this page never asked.
The check itself is verify-where-the-lateness-goes.mjs. It does not take the summary's word for anything: it recomputes every aggregate on this page from the 5,824 per-leg rows, then checks the text you are reading and the payload the page ships against that recomputation. Its --selftest plants nine defects and requires each one to turn it red.
One claim on this page is not in that program, deliberately. That the seven predictions were committed before the analysis existed is a fact about a private repository's history, so it is not something a reader can ever check, and building it into the portable check would only have made the check unrunnable outside the workspace in order to assert something the outside cannot see. It lives in preregistration-order.mjs instead, which says on its first line that it is inside-only. You are being asked to take that one on trust, and this paragraph exists so that you know you are.
What this cannot tell you
- It is 50 days of late summer on one network. The publisher keeps a rolling window and there is no archive behind it, so this is the whole record that was obtainable, not a sample of a longer one.
- It cannot see a technical minimum. The first percentile of observed run times is an upper bound on how fast a train could go, and is used as one. Slack measured this way includes any margin the planner never intended.
- It cannot attribute cause. Slack is allocated by people who know where trains run late.
- 21,249 train-days were discarded because two of their calls carry the same scheduled minute and the record has no stop-sequence column, so the order could not be reconstructed. All 21,249 are that tie and none is a decreasing sequence, which makes the discard a minute-resolution artifact rather than a data fault.
- One operator rounds its own timestamps. 3.06% of realised timestamps end in a whole minute against about 1.67 per cent if seconds were uniform, and 1 operator accounts for most of the excess by rounding every one of its stamps. The publisher warns this can happen and does not say how often; now it is counted.
Source
Ist-Daten (actual data), Open Data Platform Mobility Switzerland, Open Data Platform Mobility Switzerland (ODMCH) / opentransportdata.swiss. https://data.opentransportdata.swiss/dataset/ist-daten-v2. Terms of use: https://opentransportdata.swiss/en/terms-of-use/, which state that no registration or payment is required, that the data may be processed, analysed and published, and that opentransportdata.swiss must be cited as the source. Each day's file is recorded in data/sources.json with its URL, byte length and sha256 as received.