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.

Median arrival lateness against departure lateness, 6,485,058 legs across 737,701 train-days. The dashed diagonal is the line a railway with no slack would follow. The gap below it is what the timetable gave back, and it closes.
END TO END, POOLED OVER EVERY ROUTE
left this late (min)journeysmedian arrival delaylateness kept
0440,584-6 s -
185,00410 s 17%
227,92533 s 28%
311,20660 s 33%
45,09899 s 41%
52,898165 s 55%
61,518234 s 65%
7914299.5 s 71%
8698381 s 79%
9457450 s 83%
10442466.5 s 78%
11238536 s 81%
12194622 s 86%
13170679.5 s 87%
14104759 s 90%
15151877 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.

Seconds given back by one leg, pooled over 5,824 leg classes. It peaks at 48 s for a train 3 minutes late (281,631 traversals) and never goes higher.

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.

ONE LEG, POOLED
entered this late (min)traversalsseconds given back
03,191,09119 s
11,865,90141 s
2725,07147 s
3281,63148 s
4125,67747 s
568,90141 s
635,71244 s
721,62442 s
814,40940 s
99,86938 s
109,63623 s
115,53840 s
124,21039 s
133,20240 s
142,66035 s
153,10414 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.

lineroutescheduledlegs coveredslack share
RE5Solothurn to Bern 35 min 9/9 26.6%
RE5Bern to Solothurn 35 min 5/5 19.2%
IR37Zürich HB to Basel SBB 69 min 7/7 22.3%
IR36Basel SBB to Zürich HB 77 min 7/7 20.9%
IR70Luzern to Zürich HB 42 min 9/9 20.8%
RE13Landquart to Klosters Platz 39 min 4/4 21.8%
RE13Klosters Platz to Landquart 40 min 7/7 28.4%
RE7Chur to Ilanz 34 min 9/9 33.6%
RE7Ilanz to Chur 35 min 3/3 24.5%
IR70Zürich HB to Luzern 41 min 5/5 18.1%
RE1Davos Platz to Klosters Platz 28 min 3/3 19.8%
IR90Brig to Genève-Aéroport 160 min 13/13 21.1%
IR90Genève-Aéroport to Brig 157 min 14/14 20.3%
IR17Bern to Olten 47 min 4/4 15.9%
RE24Luzern to Olten 47 min 15/15 17.9%
RE24Olten 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 analysiswhat 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.

legcatmedian runarrival chaindeparture chainidentical departure stamp
Diessenhofen to St. KatharinentalS -151 s42 s -20 s1%
Seon Nord to SeonS -108 s63 s -13 s0%
Bellach to Solothurn AllmendS -183 s-41 s -22 s0%
St. Katharinental to DiessenhofenS -19 s166 s -30 s0%
Le Pâquier-Montbarry to La Tour-de-Trême RonclinaS -71 s117 s -46 s1%
La Tour-de-Trême Ronclina to Le Pâquier-MontbarryS -107 s130 s -89 s0%
Igis to Landquart RiedS -93 s1 s 0 s100%
Landquart Ried to IgisS -42 s60 s 0 s100%
Lessoc to AlbeuveS -275 s74 s 0 s100%
Albeuve to NeirivueS -240 s35 s -112 s1%

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

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.