Burnout comes from unpredictable, unshared paging more than from being awake at night. A rotation that is fair, well-handed-off, and rehearsed protects both the service and the people who run it.
A job that is shared
On-call work is a job that belongs to a team, not to one hero. A rotation turns ‘whoever was last awake’ into a defined roster and a schedule, so the pager load spreads across people and nobody burns out carrying the whole queue. The schedule itself — a weekly handoff, a backup/secondary on-call, a follow-the-sun structure across sites — is how you keep the obligation sustainable without letting it turn into a possession. Don’t let the urgency of tonight’s page decide who owns tomorrow; the rotation decides on purpose, ahead of time.
Handoff is half the craft
Handoff is where a rotation most often breaks. When the incoming person inherits a blank slate, what should be a handoff becomes a re-investigation. Two habits close that gap: a written handoff (what is broken, what has been tried, what the next person should own) and an overlap window so the incoming engineer is shadowed by the outgoing one before they leave. A rotation that swaps names but not context turns the same incident into a fresh one every week. The craft is not the schedule; it is the continuity the schedule protects.
Rehearse before the bell rings
The time to rehearse is before the pager rings, not during. Run the runbook in quiet hours, drill the restore path (not just the diagnosis), and map each alert to a decision tree so a cold Sunday-morning page is a checklist rather than a riddle. What you rehearse is exactly what the team will execute under adrenaline: the cheap, repeatable steps taken calmly in daylight are what keep the team from improvising during the real incident. That rehearsal is the difference between ‘we will figure it out’ and a boring, repeatable process.
When it breaks
Single-person coverage looks simple until it breaks the person. One engineer holding the pager for months stops sleeping well, dreads every notification, and eventually burns out or quits; Google’s SRE guidance treats unbounded on-call as a staffing bug, not dedication. The fix is a bounded rotation with a maximum tour length and a real off-call week where the previous primary is not the silent backup.
A broken escalation chain fails differently. If the secondary never acknowledges, contacts are stale, or paging the manager reaches a mailbox nobody reads, a 3 a.m. incident waits while the primary debugs alone. Rehearse the chain like a restore: page the secondary in business hours and confirm a human answers within minutes.
Alert fatigue is the third failure. Hundreds of noisy, unactionable alerts teach the team the pager lies, so the one real page is swiped away with the rest. Every alert needs an owner, a runbook link, and a threshold tuned after the last false positive.
Missing handoffs complete the set. The name changes on Monday with no written state, so Tuesday repeats Monday’s investigation from scratch. Require a short note — symptoms, tried steps, next move — plus a fifteen-minute overlap before the old primary goes dark.
Check it quickly
You can test fairness on paper before anyone loses a weekend. The snippet builds a five-person weekly rota, skips holiday weeks without consuming a turn, and prints the load distribution.
- Save the snippet as
rota_probe.pyin an empty directory. - Run
python3 rota_probe.pywith Python 3.9 or later; it uses only the standard library and touches no network or files. - Confirm it prints a week-by-week table, per-person counts, and a final PASS line; any assertion error means the rota logic regressed.
from collections import Counter
from datetime import date, timedelta
TEAM = ["ana", "ben", "cara", "dev", "elin"]
START = date(2027, 8, 2) # a Monday
WEEKS = 22
HOLIDAYS = {date(2027, 8, 9), date(2027, 8, 30)} # Monday public holidays
rota = [] # list of (monday, person or None)
cursor = 0
for week in range(WEEKS):
monday = START + timedelta(weeks=week)
if monday in HOLIDAYS:
rota.append((monday, None)) # holiday week: nobody on call, pointer stays
continue
person = TEAM[cursor % len(TEAM)]
rota.append((monday, person))
cursor += 1
assigned = [person for _, person in rota if person is not None]
counts = Counter(assigned)
for monday, person in rota:
label = person if person else "holiday-skip"
print(f"{monday.isoformat()} -> {label}")
print("counts:", dict(sorted(counts.items())))
# Load-balance statistics: 20 assigned weeks / 5 people = 4 each.
assert len(assigned) == WEEKS - len(HOLIDAYS) == 20, len(assigned)
assert set(counts) == set(TEAM), counts
assert all(count == 4 for count in counts.values()), dict(counts)
assert max(counts.values()) - min(counts.values()) == 0, dict(counts)
# Holiday weeks consume no turn and hold nobody.
assert all(monday not in HOLIDAYS for monday, person in rota if person is not None)
assert sum(1 for _, person in rota if person is None) == len(HOLIDAYS)
# Nobody works two calendar turns in a row (pointer only advances on assigned weeks).
for prev, current in zip(assigned, assigned[1:]):
assert prev != current, (prev, current)
print("PASS: 20 on-call weeks across 5 people, 4 each, holidays skipped, no back-to-back")
The script assigns twenty on-call weeks round-robin, leaves two holiday Mondays unassigned without advancing the pointer, and asserts three properties: each engineer holds exactly four weeks, nobody works two weeks running, and no assignment lands on a holiday. The counts expose uneven load at once, the same signal a lead should review quarterly. Checks passed with Python 3.12 on macOS; the model covers weekly turns only, not day-night splits, follow-the-sun handoffs, or holiday pay, which still need team agreement.
Further reading
Sources
- SRE Workbook: on-call, structuring fair on-call rotations, checked 2027-07-14.
- SRE book: being on-call, on-call practice and limits, checked 2027-07-14.