Docs
  • Solver
  • Models
    • Field Service Routing
    • Employee Shift Scheduling
  • Platform
Try models
  • Employee Shift Scheduling
  • Shift service constraints
  • Shift sequence patterns: daily shift pairings

Employee Shift Scheduling

    • Introduction
    • Planning AI concepts
    • Metrics and optimization goals
    • Getting started with employee shift scheduling
    • Understanding the API
    • Employee shift scheduling user guide
    • Employee resource constraints
      • Employee availability
      • Employee contracts
      • Employee contracts: period rules
      • Employee contracts: shift rules
      • Fairness
      • Pairing employees
      • Shift travel and locations
    • Shift service constraints
      • Alternative shifts
      • Demand and supply
      • Mandatory and optional visits
      • Shift assignments
      • Shift sequence patterns: single day shifts
      • Shift sequence patterns: multi-day shifts
      • Shift sequence patterns: daily shift pairings
      • Skills and risk factors
    • Recommendations
    • Real-time planning
    • Time zones and Daylight Saving Time (DST)
    • New and noteworthy
    • Upgrading to the latest versions
    • Feature requests

Shift sequence patterns: daily shift pairings

Daily shift pairing rules are used to pair shifts together.

For instance, when employees are required to work weekends, there could be a preference that they work both Saturday and Sunday, but that they don’t work two consecutive weekends.

Shift pairing rules can also be used to prohibit certain shift pairings, for instance, an employee shouldn’t work a late shift followed by an early shift the following day.

This guide explains how to manage daily shift pairing rules with the following examples:

  • Daily shift pairing rules

    • Preferred satisfiability

    • Unpreferred satisfiability

    • Prohibited satisfiability

Prerequisites

To run the examples in this guide, you need to authenticate with a valid API key for the Employee Shift Scheduling model:

  1. Log in to Timefold Platform: app.timefold.ai

  2. From the Dashboard, click your tenant, and from the drop-down menu select Tenant Settings, then choose API Keys.

  3. Create a new API key or use an existing one. Ensure the list of models for the API key contains the Employee Shift Scheduling model.

In the examples, replace <API_KEY> with the API Key you just copied.

1. Daily shift pairing rules

Daily shift pairing rules are configured in contracts:

{
  "contracts": [
    {
      "id": "fullTimeContract",
      "dailyShiftPairingRules": [
        {
          "id": "sundayAndSaturdayShift",
          "shiftTags": [
            "Saturday"
          ],
          "pairedShiftTags": [
            "Sunday"
          ],
          "satisfiability": "PREFERRED",
          "shiftTagMatches": "ALL",
          "dayOffset": 1
        }
      ]
    }
  ]
}

dailyShiftPairingRules must include an ID.

shiftTags must include a tag that is associated with the first shift that will be paired.

pairedShiftTags must include a tag that is associated with the second shift that will be paired.

shiftTagMatches can be set to ALL or ANY. The default behavior for shiftTagMatches is ALL, and if omitted, the default ALL will be used.

dayOffSet is the difference in days between start day of the shift matched by shiftTags and the start day of the shift matched by pairedShiftTags. dayOffSet can be both positive or negative. The default value is 0. When pairing shifts on subsequent days, set dayOffSet to 1.

Shifts must include tags:

{
  "id": "Mon 0300",
  "start": "2027-02-01T03:00:00Z",
  "end": "2027-02-01T07:00:00Z",
  "tags": ["Early"]
}

satisfiability of the pattern can be PREFERRED, PROHIBITED, or UNPREFERRED.

1.1. Preferred satisfiability

When the satisfiability of the rule is PREFERRED, the Employee does not have preferred daily shift pairing soft constraint is invoked, which adds a soft penalty to the run score when an employee is to a shift that matches the shiftTags but is not assigned to a shift that matches the corresponding pairShiftTags, incentivizing Timefold to use solutions with the best score.

In the following example, there are 4 shifts and 2 employees.

There is a single dailyShiftPairingRules that specifies that shifts tagged Saturday should be paired with shifts tagged Sunday.

A global rule makes sure the shifts are assigned fairly. See the fairness guide for more details about fairness.

Ann is assigned the shifts tagged Thursday and Friday, and Beth is assigned the shifts tagged Saturday and Sunday.

daily shift pairing rules preferred
  • Input

  • Output

Try this example in Timefold Platform by saving this JSON into a file called sample.json and make the following API call:
curl -X POST -H "Content-type: application/json" -H 'X-API-KEY: <API_KEY>' https://app.timefold.ai/api/models/employee-scheduling/v1/schedules [email protected]
{
  "config": {
    "run": {
      "name": "Daily shift pairing rules - preferred"
    }
  },
  "modelInput": {
    "globalRules": {
      "balanceTimeWorkedRules": [
        {
          "id": "balanceTime"
        }
      ]
    },
    "contracts": [
      {
        "id": "fullTimeContract",
        "dailyShiftPairingRules": [
          {
            "id": "sundayAndSaturdayShift",
            "shiftTags": [
              "Saturday"
            ],
            "pairedShiftTags": [
              "Sunday"
            ],
            "satisfiability": "PREFERRED",
            "shiftTagMatches": "ALL",
            "dayOffset": 1
          }
        ]
      }
    ],
    "employees": [
      {
        "id": "Ann",
        "contracts": [
          "fullTimeContract"
        ]
      },
      {
        "id": "Beth",
        "contracts": [
          "fullTimeContract"
        ]
      }
    ],
    "shifts": [
      {
        "id": "Thu",
        "start": "2027-02-04T09:00:00Z",
        "end": "2027-02-04T17:00:00Z",
        "tags": ["Thursday"]
      },
      {
        "id": "Fri",
        "start": "2027-02-05T09:00:00Z",
        "end": "2027-02-05T17:00:00Z",
        "tags": ["Friday"]
      },
      {
        "id": "Sat",
        "start": "2027-02-06T09:00:00Z",
        "end": "2027-02-06T17:00:00Z",
        "tags": ["Saturday"]
      },
      {
        "id": "Sun",
        "start": "2027-02-07T09:00:00Z",
        "end": "2027-02-07T17:00:00Z",
        "tags": ["Sunday"]
      }
    ]
  }
}
To request the solution, locate the 'ID' from the response to the post operation and append it to the following API call:
curl -X GET -H 'X-API-KEY: <API_KEY>' https://app.timefold.ai/api/models/employee-scheduling/v1/schedules/<ID>
{
  "run": {
    "id": "ID",
    "name": "Daily shift pairing rules - preferred",
    "submitDateTime": "2025-04-15T05:17:41.594630886Z",
    "startDateTime": "2025-04-15T05:17:58.30896309Z",
    "activeDateTime": "2025-04-15T05:17:58.514501331Z",
    "completeDateTime": "2025-04-15T05:18:29.561996949Z",
    "shutdownDateTime": "2025-04-15T05:18:29.905517566Z",
    "solverStatus": "SOLVING_COMPLETED",
    "score": "0hard/0medium/0soft",
    "tags": [
      "system.profile:default"
    ],
    "validationResult": {
      "summary": "OK"
    }
  },
  "modelOutput": {
    "shifts": [
      {
        "id": "Thu",
        "employee": "Ann"
      },
      {
        "id": "Fri",
        "employee": "Ann"
      },
      {
        "id": "Sat",
        "employee": "Beth"
      },
      {
        "id": "Sun",
        "employee": "Beth"
      }
    ]
  },
  "inputMetrics": {
    "employees": 2,
    "shifts": 4,
    "pinnedShifts": 0
  },
  "kpis": {
    "assignedShifts": 4,
    "unassignedShifts": 0,
    "assignedShiftGroups": 0,
    "unassignedShiftGroups": 0,
    "workingTimeFairnessPercentage": 100.0,
    "disruptionPercentage": 0.0,
    "averageDurationOfEmployeesPreferencesMet": null,
    "minimumDurationOfPreferencesMetAcrossEmployees": null,
    "averageDurationOfEmployeesUnpreferencesViolated": null,
    "maximumDurationOfUnpreferencesViolatedAcrossEmployees": null,
    "activatedEmployees": 2,
    "assignedMandatoryShifts": 4,
    "assignedOptionalShifts": 0,
    "travelDistance": 0
  }
}

modelOutput contains the schedule with Ann assigned the shifts tagged Thursday and Friday and Beth assigned the shifts tagged Saturday and Sunday.

inputMetrics provides a breakdown of the inputs in the input dataset.

KPIs provides the KPIs for the output including:

{
  "assignedShifts": 4,
  "workingTimeFairnessPercentage": 100.0,
  "activatedEmployees": 2,
  "assignedMandatoryShifts": 4
}

1.2. Unpreferred satisfiability

When the satisfiability of the rule is UNPREFERRED, the Employee has unpreferred daily shift pairing soft constraint is invoked.

{
  "dailyShiftPairingRules": [
    {
      "id": "noConsecutiveWeekends",
      "shiftTags": [
        "Weekend"
      ],
      "pairedShiftTags": [
        "Weekend"
      ],
      "satisfiability": "UNPREFERRED",
      "shiftTagMatches": "ALL",
      "dayOffset": 7
    }
  ]
}

This constraint adds a soft penalty to the run score when an employee is assigned a shift that matches the rules’s shiftTags and they are assigned to a shift that matches the rule’s pairedShiftTags on a day defined by the rule’s dayOffset attribute. The soft score penalty incentivizes Timefold to find an alternative solution.

In the following example, there are 4 weekend shifts over 2 weeks and only 1 employee. There is a single dailyShiftPairingRules that specifies that shifts tagged Weekend that occur 7 days apart should preferrably not be assigned to the same employee.

Ann is assigned all four shifts and the solution is penalized with a negative soft score.

daily shift pairing rules unpreferred

  • Input

  • Output

Try this example in Timefold Platform by saving this JSON into a file called sample.json and make the following API call:
curl -X POST -H "Content-type: application/json" -H 'X-API-KEY: <API_KEY>' https://app.timefold.ai/api/models/employee-scheduling/v1/schedules [email protected]
{
  "config": {
    "run": {
      "name": "Daily shift pairing rules - unpreferred"
    }
  },
  "modelInput": {
    "contracts": [
      {
        "id": "fullTimeContract",
        "dailyShiftPairingRules": [
          {
            "id": "noConsecutiveWeekends",
            "shiftTags": [
              "Weekend"
            ],
            "pairedShiftTags": [
              "Weekend"
            ],
            "satisfiability": "UNPREFERRED",
            "shiftTagMatches": "ALL",
            "dayOffset": 7
          }
        ]
      }
    ],
    "employees": [
      {
        "id": "Ann",
        "contracts": [
          "fullTimeContract"
        ]
      }
    ],
    "shifts": [
      {
        "id": "Sat 1",
        "start": "2027-02-06T09:00:00Z",
        "end": "2027-02-06T17:00:00Z",
        "tags": ["Weekend"]
      },
      {
        "id": "Sun 1",
        "start": "2027-02-07T09:00:00Z",
        "end": "2027-02-07T17:00:00Z",
        "tags": ["Weekend"]
      },
      {
        "id": "Sat 2",
        "start": "2027-02-13T09:00:00Z",
        "end": "2027-02-13T17:00:00Z",
        "tags": ["Weekend"]
      },
      {
        "id": "Sun 2",
        "start": "2027-02-14T09:00:00Z",
        "end": "2027-02-14T17:00:00Z",
        "tags": ["Weekend"]
      }
    ]
  }
}
To request the solution, locate the 'ID' from the response to the post operation and append it to the following API call:
curl -X GET -H 'X-API-KEY: <API_KEY>' https://app.timefold.ai/api/models/employee-scheduling/v1/schedules/<ID>
{
  "run": {
    "id": "ID",
    "name": "Daily shift pairing rules - unpreferred",
    "submitDateTime": "2025-04-15T06:19:36.737780839Z",
    "startDateTime": "2025-04-15T06:19:48.000200061Z",
    "activeDateTime": "2025-04-15T06:19:48.179594336Z",
    "completeDateTime": "2025-04-15T06:20:19.1089142Z",
    "shutdownDateTime": "2025-04-15T06:20:19.369215721Z",
    "solverStatus": "SOLVING_COMPLETED",
    "score": "0hard/0medium/-1920soft",
    "tags": [
      "system.profile:default"
    ],
    "validationResult": {
      "summary": "OK"
    }
  },
  "modelOutput": {
    "shifts": [
      {
        "id": "Sat 1",
        "employee": "Ann"
      },
      {
        "id": "Sun 1",
        "employee": "Ann"
      },
      {
        "id": "Sat 2",
        "employee": "Ann"
      },
      {
        "id": "Sun 2",
        "employee": "Ann"
      }
    ]
  },
  "inputMetrics": {
    "employees": 1,
    "shifts": 4,
    "pinnedShifts": 0
  },
  "kpis": {
    "assignedShifts": 4,
    "unassignedShifts": 0,
    "assignedShiftGroups": 0,
    "unassignedShiftGroups": 0,
    "workingTimeFairnessPercentage": null,
    "disruptionPercentage": 0.0,
    "averageDurationOfEmployeesPreferencesMet": null,
    "minimumDurationOfPreferencesMetAcrossEmployees": null,
    "averageDurationOfEmployeesUnpreferencesViolated": null,
    "maximumDurationOfUnpreferencesViolatedAcrossEmployees": null,
    "activatedEmployees": 1,
    "assignedMandatoryShifts": 4,
    "assignedOptionalShifts": 0,
    "travelDistance": 0
  }
}

modelOutput contains the schedule with Ann assigned to all 4 weekend shifts.

inputMetrics provides a breakdown of the inputs in the input dataset.

KPIs provides the KPIs for the output including:

{
  "assignedShifts": 4,
  "activatedEmployees": 1,
  "assignedMandatoryShifts": 4
}

1.3. Prohibited satisfiability

When the satisfiability of the rule is PROHIBITED, the Employee has prohibited daily shift pairing hard constraint is invoked.

{
  "contracts": [
    {
      "id": "fullTimeContract",
      "dailyShiftPairingRules": [
        {
          "id": "noEarlyShiftAfterLate",
          "shiftTags": [
            "Late"
          ],
          "pairedShiftTags": [
            "Early"
          ],
          "satisfiability": "PROHIBITED",
          "shiftTagMatches": "ALL",
          "dayOffset": 1
        }
      ]
    }
  ]
}

Shifts will be left unassigned if assigning them would break Employee has prohibited daily shift pairing constraint.

In the following example, there is one dailyShiftPairingRules:

Shifts tagged "Late" and "Early" are prohibited from being assigned together to individual employees when the "Early" shift is the day after the "Late" shift.

Ann is assigned the shift tagged "Late", but the shift tagged "Early" is left unassigned to avoid breaking the hard constraint.

daily shift pairing rules prohibited
  • Input

  • Output

Try this example in Timefold Platform by saving this JSON into a file called sample.json and make the following API call:
curl -X POST -H "Content-type: application/json" -H 'X-API-KEY: <API_KEY>' https://app.timefold.ai/api/models/employee-scheduling/v1/schedules [email protected]
{
  "config": {
    "run": {
      "name": "Daily shift pairing rules - prohibited"
    }
  },
  "modelInput": {
    "contracts": [
      {
        "id": "fullTimeContract",
        "dailyShiftPairingRules": [
          {
            "id": "noEarlyShiftAfterLate",
            "shiftTags": [
              "Late"
            ],
            "pairedShiftTags": [
              "Early"
            ],
            "satisfiability": "PROHIBITED",
            "shiftTagMatches": "ALL",
            "dayOffset": 1
          }
        ]
      }
    ],
    "employees": [
      {
        "id": "Ann",
        "contracts": [
          "fullTimeContract"
        ]
      }
    ],
    "shifts": [
      {
        "id": "Mon Late",
        "start": "2027-02-01T16:00:00Z",
        "end": "2027-02-02T00:00:00Z",
        "tags": ["Late"]
      },
      {
        "id": "Tue Early",
        "start": "2027-02-02T00:00:00Z",
        "end": "2027-02-02T08:00:00Z",
        "tags": ["Early"]
      }
    ]
  }
}
To request the solution, locate the 'ID' from the response to the post operation and append it to the following API call:
curl -X GET -H 'X-API-KEY: <API_KEY>' https://app.timefold.ai/api/models/employee-scheduling/v1/schedules/<ID>
{
  "run": {
    "id": "ID",
    "name": "Daily shift pairing rules - prohibited",
    "submitDateTime": "2025-04-15T08:03:38.67979918Z",
    "startDateTime": "2025-04-15T08:03:49.9208183Z",
    "activeDateTime": "2025-04-15T08:03:50.015353229Z",
    "completeDateTime": "2025-04-15T08:04:20.916004482Z",
    "shutdownDateTime": "2025-04-15T08:04:21.106144893Z",
    "solverStatus": "SOLVING_COMPLETED",
    "score": "0hard/-1medium/0soft",
    "tags": [
      "system.profile:default"
    ],
    "validationResult": {
      "summary": "OK"
    }
  },
  "modelOutput": {
    "shifts": [
      {
        "id": "Mon Late",
        "employee": "Ann"
      },
      {
        "id": "Tue Early",
        "employee": null
      }
    ]
  },
  "inputMetrics": {
    "employees": 1,
    "shifts": 2,
    "pinnedShifts": 0
  },
  "kpis": {
    "assignedShifts": 1,
    "unassignedShifts": 1,
    "assignedShiftGroups": 0,
    "unassignedShiftGroups": 0,
    "workingTimeFairnessPercentage": null,
    "disruptionPercentage": 0.0,
    "averageDurationOfEmployeesPreferencesMet": null,
    "minimumDurationOfPreferencesMetAcrossEmployees": null,
    "averageDurationOfEmployeesUnpreferencesViolated": null,
    "maximumDurationOfUnpreferencesViolatedAcrossEmployees": null,
    "activatedEmployees": 1,
    "assignedMandatoryShifts": 1,
    "assignedOptionalShifts": 0,
    "travelDistance": 0
  }
}

modelOutput contains the schedule with Ann assigned to the shift tagged "Late" and the shift tagged "Early" left unassigned.

inputMetrics provides a breakdown of the inputs in the input dataset.

KPIs provides the KPIs for the output including:

{
  "assignedShifts": 1,
  "unassignedShifts": 1,
  "activatedEmployees": 1,
  "assignedMandatoryShifts": 1
}

Next

  • See the Employee shift scheduling user guide

  • Understand the constraints of the Employee Shift Scheduling model.

  • See the full API spec or try the online API.

  • Manage schedules with Time zones and Daylight Saving Time (DST) changes.

  • Working with Employee availability.

  • © 2025 Timefold BV
  • Timefold.ai
  • Documentation
  • Changelog
  • Send feedback
  • Privacy
  • Legal
    • Light mode
    • Dark mode
    • System default