Skip to content

Mixed-SKU palletizing: three packers, one rule

Shelf first-fit-decreasing, maximal-rectangles and interlocking pattern search — what each is good at, and the stated rule that picks your plan.

A single-SKU pallet is a tiling problem with one tile. A mixed-SKU pallet is a different problem every time, and no one heuristic wins on every order — which is why this engine does not have a packer, it has 3, runs all of them on every order, and publishes what each achieved next to the one it chose.

The three packers

shelf — first-fit-decreasing into shelves. Sort by footprint, lay cases in rows, start a new row when the current one is full, start a new layer when the pallet is. Fast, predictable, and strongest when an order is mostly a few repeated case sizes.

maxrects — maximal-rectangles free-space packing. Keeps the list of empty rectangles and places each case into the one that wastes least, splitting what is left. Beats shelf packing when case sizes are irregular, because it can use the offcuts a shelf row leaves.

interlock — guillotine pattern search that alternates patterns between layers so the seams of one layer sit over the bodies of the layer below. Usually gives up a little density for a stack that holds itself together; the effect is largest on uniform cases, which are exactly the loads that shear apart in a column stack.

The rule that picks one

1. cases placed        — more is better
2. admissible          — a valid plan beats an invalid one
3. volume density      — higher is better
4. stability score     — higher is better
5. strategy name       — alphabetically earlier wins
portfolioBetter(), web/lib/palletizer/optimizer.ts. The name is the final tiebreak, so the choice is reproducible.

Cases placed comes first because a denser pallet that leaves two cases on the floor is not a better pallet, it is a second pallet. Validity ranks above density for the same reason: an inadmissible arrangement does not have a density, it has a defect. Every plan carries the losing strategies’ figures in strategy_comparison, so you can see the margin the choice was made by rather than trusting that a choice was made at all.

Constraints the packer understands

heavy_low sorts by weight rather than height, so mass ends up near the deck. fragile_high withholds cases above a fragility threshold until everything else is placed, so nothing is stacked on them; protect_sku_ids names specific SKUs to treat that way regardless of their fragility value. speed_mode forbids 90° rotations: fewer wrist moves, a faster cycle, usually a lower density. The cycle model is explicit about that trade — 7.5 s per case plus 1.8 s for each rotated one, before travel.

Orders bigger than a pallet

An order is packed onto as many pallets as it needs: pack a pallet, remove what it took, repeat. Each pallet is an ordinary sealed plan with its own hash, so they can be verified, exported and built independently. Every split also reports a lower bound on the pallet count, computed from total volume and mass against the deck’s capacity (1219 × 1016 mm, 1800 mm high, 1000 kg by default) — which is how you find out how much room is left.

Pure greed packs each pallet from everything still unplaced, and that is where mixed heights hurt twice: a layer is as tall as its tallest case, so the pallet is loose, and the cases it leaves behind are the ones the next pallet has to take. So a pallet that comes out below 0.85 density is packed again from the tallest 3 height families, and whichever pallet carries more volume is the one taken — with admissibility ahead of volume, because an inadmissible pallet is a defect rather than a cheaper pallet. On the mixed 300-case reference order that is six pallets instead of seven, every one of them admissible, against a lower bound of four. On an order of uniform cases it changes nothing and costs nothing: the first pallet is already above the threshold, so the second pack never runs.

It is still greedy. Nothing here moves a case back to an earlier pallet, and there is no exact solver. The bound is published next to the count so the remaining gap is yours to see.

Determinism, and why it is the point

The same order produces the same arrangement and the same SHA-256 in the browser, the CLI and the Python package — CI compares both engines on every commit, strategy by strategy. That is what makes a plan citable: a hash in an email names one arrangement that passed one set of thresholds, and anyone can re-derive it without trusting the sender.

Next: turning a plan into motion a robot can run, and how the stability number is computed.

Upload your own SKU CSV and get a validated, sealed plan in the browser.

The engine is Apache-2.0 and unmetered on your own machines — what the hosted plans add is a signature only this service can make, state it keeps for you, and compute on its hardware.

Every figure on this page is imported from the code that uses it, so it cannot drift from the engine. Run the engine on your own SKUs or check a plan you already have. The limits are written down in known risks.