DEV Community

Cover image for Effective On-Call Rotations: Lessons From Building Fair Schedules
Samson Tanimawo
Samson Tanimawo

Posted on

Effective On-Call Rotations: Lessons From Building Fair Schedules

The Rotation Nobody Wants

Our on-call rotation was a spreadsheet. Updated manually. Someone always got scheduled during their vacation. Two people occasionally got double-booked. Holidays were a battleground.

Designing Fair Rotations

Principle 1: Equal Burden Distribution

Track total on-call hours, not just shift count:

def calculate_oncall_burden(engineer, period_days=90):
shifts = get_shifts(engineer, period_days)
return {
'total_hours': sum(s.duration_hours for s in shifts),
'weekend_hours': sum(s.duration_hours for s in shifts if s.is_weekend),
'holiday_hours': sum(s.duration_hours for s in shifts if s.is_holiday),
'night_hours': sum(s.duration_hours for s in shifts if s.is_night),
'pages_received': sum(s.page_count for s in shifts),
'burden_score': calculate_weighted_score(shifts)
}

def calculate_weighted_score(shifts):
"""Weight different types of on-call differently."""
score = 0
for s in shifts:
base = s.duration_hours
if s.is_weekend: base *= 1.5
if s.is_holiday: base *= 2.0
if s.is_night: base *= 1.3
score += base
return round(score, 1)
Enter fullscreen mode Exit fullscreen mode

Principle 2: Respect Preferences

onCall_preferences:
alice:
blackout_dates: ["2024-03-25", "2024-04-01:2024-04-05"] # Vacation
preferred_days: ["Mon", "Tue", "Wed"] # Family on weekends
max_consecutive_days: 3

bob:
blackout_dates: ["2024-04-10"]
preferred_days: ["any"]
max_consecutive_days: 7
prefers_weekends: true # Weekend differential pay
Enter fullscreen mode Exit fullscreen mode

Principle 3: Minimum Pool Size

The math on sustainable rotations:

Pool size Frequency Burnout risk
3 people 1 week on / 2 off HIGH unsustainable
4 people 1 week on / 3 off MEDIUM barely okay
5 people 1 week on / 4 off LOW comfortable
6+ people 1 week on / 5+ off MINIMAL ideal

Rule: Minimum 5 people per rotation.
If you have fewer, reduce on-call scope or hire.
Enter fullscreen mode Exit fullscreen mode

Principle 4: Escalation Tiers

escalation_chain:
tier_1: # Primary on-call
response_time: 5 minutes
scope: all pages

tier_2: # Secondary on-call (backup)
response_time: 15 minutes
scope: escalated or unacknowledged

tier_3: # Engineering manager
response_time: 30 minutes
scope: P1 only or when both T1+T2 unavailable

tier_4: # CTO/VP Engineering
response_time: 60 minutes
scope: Extended P1 (>1 hour), customer escalation
Enter fullscreen mode Exit fullscreen mode

The Override System

Life happens. Make swaps easy:

def request_swap(requesting_engineer, target_date, volunteer=None):
"""Allow easy on-call swaps."""

if volunteer:
# Direct swap: Alice asks Bob to cover
execute_swap(requesting_engineer, volunteer, target_date)
notify_team(f"{requesting_engineer} swapped with {volunteer} for {target_date}")
else:
# Open request: Alice needs coverage, anyone can take it
post_to_channel(
f"{requesting_engineer} needs coverage for {target_date}. "
f"Reply to volunteer. Comp: standard on-call rate."
)

# Key: NO manager approval needed for swaps
# This reduces friction dramatically
Enter fullscreen mode Exit fullscreen mode

Holiday Fairness

The holiday rotation is separate and tracked year-over-year:

holidays_2024 = [
'New Years', 'MLK Day', 'Presidents Day', 'Memorial Day',
'July 4th', 'Labor Day', 'Thanksgiving', 'Christmas'
]

def assign_holidays(team, year):
# Get historical holiday assignments
history = get_holiday_history(team, years=3)

# Sort by who has covered the FEWEST holidays recently
sorted_team = sorted(team, key=lambda e: history.get(e, 0))

assignments = {}
for i, holiday in enumerate(holidays_2024):
engineer = sorted_team[i % len(sorted_team)]
assignments[holiday] = engineer

return assignments
Enter fullscreen mode Exit fullscreen mode

Metrics We Track

Metric Target Current
Burden score variance < 15% 8%
Swap request fulfillment > 95% 98%
Pages per shift (average) < 3 1.8
NPS for on-call experience > 0 +32
Holiday coverage fairness < 1 shift variance 0.5

If you want AI-powered on-call scheduling that optimizes for fairness automatically, check out what we're building at Nova AI Ops.


Written by Dr. Samson Tanimawo
BSc · MSc · MBA · PhD
Founder & CEO, Nova AI Ops. https://novaaiops.com

Top comments (0)