> ## Documentation Index
> Fetch the complete documentation index at: https://developers.kardinal.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Objectives and how they're ranked

> How the engine compares solutions one objective at a time, what each built-in objective measures, and how to choose an order.

A plan's `objectives` list isn't a set of weights. It's a ranking: the engine compares two candidate solutions one objective at a time, in the order you give, and the first objective that separates them decides which one wins. This page explains that comparison, what each objective measures, and how to choose an order for your own operation.

## How two solutions are compared

The engine looks at the first objective in the list. If one solution is better on it, that solution wins — whatever happens on every later objective. Only when the two solutions are tied on the first objective does the engine look at the second, and so on down the list.

No amount of improvement on a later objective can make up for a loss on an earlier one. There are no weights to tune and no exchange rate between objectives: the position in the list is the only lever.

Take three candidate solutions for the same plan:

| Solution | Mandatory stops planned | Total delay | Vehicles used |
| -------- | ----------------------- | ----------- | ------------- |
| A        | 100                     | 30 min      | 5             |
| B        | 100                     | 10 min      | 6             |
| C        | 99                      | 0 min       | 3             |

With the default order (`maximizeMandatoryStops`, then `minimizeDelay`, then … `minimizeResources`):

* C loses straight away: it plans one mandatory stop fewer, even though it's better on every other line.
* A and B tie on mandatory stops, so delay decides: B wins, even though it needs an extra vehicle.

Move `minimizeResources` ahead of `minimizeDelay` and A beats B instead: you've told the engine that fleet size matters more than on-time delivery, so it lets stops run late to avoid dispatching an extra vehicle. C still loses under both orders, because `maximizeMandatoryStops` stays first.

### Objectives split by priority

Three objectives depend on `priority`: `maximizeMandatoryStops` and `maximizeOptionalStops` (from each order's `priority`) and `minimizeResources` (from each resource's `priority`). For these, the engine generates one objective per `priority` value present in the plan, and keeps them at the position of the original objective in the list. Lower numbers matter more, and negative values are allowed.

* **Stops are maximized from the most important level down.** With mandatory stops at priorities `0` and `1`, `maximizeMandatoryStops` becomes two successive objectives: first maximize the priority-`0` mandatory stops, then the priority-`1` ones. One more planned priority-`0` stop always wins over any number of priority-`1` stops: priority levels are strict tiers, not weights.
* **Resources are minimized from the least important level up.** With resources at priorities `0`, `1`, and `2`, `minimizeResources` first minimizes the priority-`2` resources in use, then the priority-`1` ones, then the priority-`0` ones — so the least important resources are the first left unused.

## The default list

If a plan doesn't set `objectives`, the engine uses this order:

```json theme={null}
"objectives": [
  "maximizeMandatoryStops",
  "minimizeDelay",
  "minimizeCosts",
  "minimizeResources",
  "minimizeOverOverlappingCapacitiesOnStops",
  "maximizeOptionalStops",
  "maximizePreferredStops",
  "minimizeWorkingDuration",
  "minimizeDistance"
]
```

In plain terms: first plan as many mandatory stops as possible; among solutions that do, prefer less customer delay; then lower cost; then fewer vehicles; then fewer breaches of `overlappingCapacitiesByStopTag` limits; then more optional stops; then more stops matched to a resource's `preferredStopTags`; then less total working time; then less distance.

Treat it as a starting point to check against your plan, not a template to copy. Several entries do nothing unless your plan declares the data they measure (see each objective below), and an objective your operation cares about may be missing from it.

## Built-in objectives

Each built-in objective is a plain string in the list. The ones marked *needs data* measure something your plan has to declare: without it, the objective has nothing to optimize, doesn't raise an error, and is removed from the optimization — it then doesn't appear in the solution's `objectives` either. Drop those from the list rather than keeping a guaranteed no-op.

### maximizeMandatoryStops

Maximizes the number of mandatory (non-optional) stops planned. First in the default list, so nothing ranked after it can justify leaving a mandatory stop unplanned. A mandatory order with a low `priority` still counts, in a later tier (see [Objectives split by priority](#objectives-split-by-priority)): priority doesn't let an order be dropped, only `"optional": true` does (see `maximizeOptionalStops`).

### minimizeDelay

Minimizes the sum of delays against stops' `preferredTimeWindows`. Only lateness counts: the engine never plans a stop before its preferred window opens, so arriving early is never traded against it. *Needs data*: at least one `preferredTimeWindows`. A plan whose windows are all hard `authorizedTimeWindows` has no delay to minimize — see [Hard vs soft constraints](/concepts/hard-vs-soft-constraints).

### minimizeCosts

Minimizes the plan's total cost, as defined by each resource's `cost` object (a flat `using` cost per resource used, per-kilometre `km`, per-hour `workedHours`, per-capacity costs, and so on). *Needs data*: at least one resource with a `cost`. See [Cost modeling](/guides/cost-modeling).

### minimizeResources

Minimizes the number of resources used. Split by `Resource.priority` and minimized from the least important level (highest number) up, so those resources are the first left unused (see [Objectives split by priority](#objectives-split-by-priority)). It counts resources directly: no `using` cost is needed for it to work.

### minimizeOverOverlappingCapacitiesOnStops

Minimizes how far the limits set in `overlappingCapacitiesByStopTag` are exceeded — the number of resources simultaneously present at stops sharing a tag, beyond the limit set for that tag. The limit is a soft target, not a hard cap: whether the engine accepts an overrun to do better elsewhere depends on where this objective sits in the list. *Needs data*: plan-level `overlappingCapacitiesByStopTag`. See [Limiting simultaneous depot visits](/guides/multi-trip-tours#limiting-simultaneous-depot-visits).

### maximizeOptionalStops

Maximizes the number of optional stops planned, in addition to the mandatory ones, split by `priority` like `maximizeMandatoryStops`. Marking an order `"optional": true` takes it out of `maximizeMandatoryStops`, so this objective is the only reason the engine plans it: without it in the list, an optional stop is unlikely to be planned at all, since it adds distance and time without serving any other objective. In the default order it comes after `minimizeResources`, so the engine never adds a vehicle just to plan an optional stop. *Needs data*: at least one order marked `"optional": true`.

### maximizePreferredStops

Maximizes the stops served by a resource whose `preferredStopTags` match the stop's tags — a soft preference, where another resource can still take the stop when that does better on a higher-ranked objective. *Needs data*: at least one resource with `preferredStopTags` matching some stops. See [Driver skills and qualifications](/guides/advanced-constraints#driver-skills-and-qualifications).

### minimizeLargestTourDuration

Minimizes the working duration of the single longest tour in the fleet — travel, service, and waiting time included — which narrows the gap between that tour and the others. Not in the default list. It's an objective the engine improves as far as it can, not a limit it enforces: use a resource's `maxWorkingDuration` for a hard bound.

### minimizeWorkingDuration

Minimizes the total working duration summed across every tour, travel, service, and waiting time included.

### minimizeDistance

Minimizes the total distance travelled, summed across every tour.

### minimizeEarlyLoadings

Minimizes early loadings, as declared per resource in `avoidEarlyLoadings` or per resource tag in the plan-level `avoidEarlyLoadingsByResourceTag`: each declaration names a stop tag, and optionally the capacities to consider. Not in the default list: add it explicitly, or the declarations have no effect. *Needs data*: at least one such declaration.

## Object entries

Two kinds of entries are objects rather than names.

### maximizePrecedences

Rewards the engine for respecting "this stop tag before that stop tag" rules. It takes a `precedences` array of pairs, each with a `previous` and a `next` stop tag, and the engine tries to satisfy as many pairs as possible. As an objective, it's a preference, not a hard rule: a stop's position inside an order's `stops` array is the only precedence the engine always enforces.

```json theme={null}
"objectives": [
  "maximizeMandatoryStops",
  { "type": "maximizePrecedences", "precedences": [{ "previous": "sector1", "next": "sector2" }] },
  "minimizeDistance"
]
```

### Custom objectives

A `custom` entry optimizes a cost expression of your own, built from `costsByResourceTag`, in the `direction` you choose (`minimize` or `maximize`), under a `name` that must not collide with a built-in objective. When a custom objective replaces `minimizeCosts`, put it at the exact position `minimizeCosts` held: anywhere else, it's traded off against different objectives and can return a different plan. See [Cost modeling](/guides/cost-modeling#rebuilding-the-default-objective-list-with-customcost).

## Choosing an order

* **Put first what you'd never trade away.** For most operations that's `maximizeMandatoryStops`: nothing ranked after it can then justify leaving a mandatory stop unplanned.
* **Check every adjacent pair, not just the first two.** Swapping any two neighbours changes which solutions win. `minimizeCosts` before `minimizeResources` prioritizes total cost even if reaching it takes an extra vehicle; the reverse caps fleet size first and lets cost settle wherever that leaves it.
* **Drop the objectives your plan gives no data to**, and add the ones it needs: `minimizeLargestTourDuration` to balance tours, `minimizeEarlyLoadings` for declared early loading rules.
* **Keep preferences in objectives, rules in constraints.** A hard constraint is never traded off, whatever the order; only soft constraints are arbitrated by the list. See [Hard vs soft constraints](/concepts/hard-vs-soft-constraints).

## Reading objectives in a solution

A solution reports the achieved value of each objective in `objectives`, with its `name`, `direction` (`minimize` or `maximize`), and `value`. Values are in each objective's natural unit: for example a count of planned stops for `maximizeMandatoryStops`, a count of resources for `minimizeResources`, kilometres for `minimizeDistance`, and hours for `minimizeWorkingDuration`. The three objectives split by priority have one entry per priority level, each with a `priority` field giving the level it covers — a plan with mandatory stops at priorities `0` and `1` returns two `maximizeMandatoryStops` entries. Objectives removed for lack of data don't appear. Comparing these values between two versions of a plan shows which objective an update actually moved.

## See also

* [How the optimization engine works](/concepts/how-the-optimization-engine-works) — the time/quality trade-off and how updates re-optimize a plan.
* [Hard vs soft constraints](/concepts/hard-vs-soft-constraints) — what the objectives arbitrate, and what they never bend.
* [Cost modeling](/guides/cost-modeling) — giving `minimizeCosts` and custom objectives something to optimize.
* [`objectives` in the API reference](/api-reference/plan/create-a-plan#body-objectives) — the field's schema.
