What “balanced” means here, and the 10-into-3 example
Every split this tool produces obeys one invariant: no team is more than one person bigger than any other. Ten people into three teams gives 4/3/3. It never gives 5/3/2, and it never leaves anybody over. The arithmetic is deliberately dull, divide and then hand the remainder out one person at a time to the first teams in the list, and the tests pin that exact worked example down.
The rule is enforced in both split modes, which is where it becomes opinionated. Ask for teams of four out of ten people and the obvious reading is 4/4/2. Instead, the team count is computed as ten divided by four rounded up, which is three, and those three teams are then balanced to 4/3/3. The reasoning is practical: a room of teams of four plus a stranded pair is almost never what the organiser wanted, and the differ-by-at-most-one promise then holds no matter which mode you picked. If you genuinely need exactly four per team and do not care about the remainder, ask for three teams and read the sizes off the result.
Requesting more teams than you have people clamps to one person each rather than producing empty teams. A setting that would yield a single team is refused outright, since there is nothing there to split.
Two ways to say how you want it split
A teacher planning group work thinks in groups: five stations, so five groups. A five-a-side organiser thinks in team size: whoever turns up, sides of five. Split by switches between the two, and the number field next to it relabels itself, so you are never guessing which of the two you are typing into. Everything downstream, the balance rule and every constraint check, behaves identically either way.
Keep-together and keep-apart, solved by search rather than luck
Both rule types take a comma-separated list of names. Matching is case-insensitive, so typing ana finds Ana and the result shows the roster’s own spelling. A token matching nobody is flagged live under the row instead of being silently ignored, which is what catches a misspelling before you generate rather than after somebody notices they were left out of their own rule.
Keep-together rules merge transitively. Ana with Ben, plus Ben with Carla, becomes one group of three rather than two overlapping pairs. That is a union-find pass over the roster, and it means you can state the relationships you actually know without computing their closure by hand. Everyone unmentioned becomes a group of one.
Keep-apart rules then become a conflict table between those groups: two groups conflict when any apart rule names a member of each. Assignment is a randomised backtracking search over groups and teams. Each group tries teams in its own shuffled order, and a placement that leads to a dead end is undone and retried elsewhere. That matters more than it sounds. The naive approach is to shuffle everybody and check whether the rules happen to hold, which spins forever on a hard instance and gives up on a satisfiable one; a complete search finds an arrangement whenever one exists.
Larger groups are placed first so an impossible packing fails fast, and there is a hard ceiling of 200,000 search steps, so a pathological input reports an honest failure instead of freezing the tab.
When your rules cannot all be satisfied
Three kinds of impossibility are caught before any searching starts, each with its own message. A keep-together group of six when the biggest team has five spots. A keep-apart group of five when there are only three teams, since two of the five would have to share. And a straight contradiction, where one rule glues Ana to Carla while another insists they stay apart, including the transitive version of that where the gluing happened through Ben.
A fourth kind only the search can detect. Three keep-together pairs split into two teams of three is unsatisfiable: no pair can be broken and no team can hold one and a half pairs. When the search exhausts, you are told the team sizes it was working with and asked to loosen a rule or change the split. Nothing is ever handed back that quietly violates a rule you set.
Fisher-Yates over crypto.getRandomValues, not Math.random
A draw that decides who plays with whom carries a genuine fairness expectation, so the shuffle is an unbiased Fisher-Yates over crypto.getRandomValues, with rejection sampling so the index distribution stays flat. Math.random is fine for decorative randomness and is not used here. Three separate things are shuffled on every run: the order groups are placed in, the order of teams each group tries, and the member order inside each finished team. Re-shuffle therefore produces a genuinely different arrangement, not a rotation of the previous one.
Running a draw, copying it, printing it
A full draw is five steps:
- Paste your roster into the Names box, one name per line or separated by commas.
- Set Split by to either Number of teams or People per team, then give it the number.
- Add any rules with + Keep together rule and + Keep apart rule, listing the names in one row separated by commas.
- Press Generate teams. The same button becomes Re-shuffle teams afterwards, so you can keep drawing until the split looks reasonable.
- Take the result with Copy results or Print.
Copy results puts a plain-text version on your clipboard, one block per team with a headcount, ready for a group chat. Print opens a self-contained page in a new window and calls your print dialog; names are HTML-escaped on the way in, so a mischievous entry prints as inert text. Nothing you paste is transmitted anywhere, which is the actual reason to prefer this over a server-side generator when the list is a real class register or staff roster.
What this deliberately does not do is balance by ability. There is no rating, no seeding, no attempt to make the sides evenly matched beyond headcount, because the tool has no idea who is any good. For a single pick rather than a full split, use Wheel of Names. To run a tournament once the teams exist, use the Bracket Generator, and to track each team’s work afterwards, give each one a Kanban Board.

