Operations Research

Seating a wedding is an optimization problem

A look at the mathematics hidden inside a wedding seating plan: graph structure, compatibility scores, hard constraints, fairness, and why arranging a few tables can become surprisingly difficult.

11 min read
Wedding seating plan

Seating a wedding is an optimization problem

A few months ago, one of my close friends got married.

Most parts of wedding planning are complicated, but at least they have a clear shape. A venue has a capacity. A menu has prices. A photographer is either available or not. You make a decision, move on, and hope nobody starts a family argument about chair covers.

The seating plan is different.

It starts innocently enough: a spreadsheet, a list of guests, a few table sizes, and some obvious groups. Family here. University friends there. Work people somewhere sensible. Easy.

Then reality arrives.

Some people should sit together. Some should absolutely not. Some guests are coming alone and need to be placed with care. Some people are great in a group but awkward one-to-one. Some friendships are obvious. Some histories are complicated. And suddenly you are no longer arranging names into boxes. You are designing several hours of social interaction.

That is what makes wedding seating interesting. On the surface, it looks like admin. Underneath, it has the structure of a difficult optimisation problem.

The goal is not simply to give everyone a chair. The goal is to create tables that actually work.

From guest list to graph

The first useful move is to stop thinking of the guest list as a list and start thinking of it as a network.

Let GG be the set of guests and TT be the set of tables. For every pair of guests ii and jj, we assign a relationship score wijw_{ij}.

Positive scores mean the pair is likely to enjoy sitting together. Scores close to zero mean the pair is neutral. Negative scores mean the pairing should probably be avoided, unless the aim is to turn the wedding breakfast into a controlled social experiment.

This gives us a weighted graph:

  • guests are nodes
  • relationships are edges
  • edge weights describe compatibility
Example compatibility graph for four wedding guests

A small compatibility graph for four guests.

Once the problem is represented this way, the question changes.

It is no longer:

Where do we put everyone?

It becomes:

How do we split this graph into tables so that each table has good internal chemistry?

That is a much better question. It is also a much harder one.

What are we optimising?

A table is not just a collection of guests. It is a collection of pairwise interactions.

That distinction matters. We are not scoring people individually. We are scoring the social environment created by putting them together.

For each table tt, define the table happiness score as:

Ht=iGjGj>iwijzijtH_t = \sum_{i \in G} \sum_{j \in G \mid j > i} w_{ij} z_{ijt}

Here, zijtz_{ijt} is a binary variable equal to 1 if guests ii and jj both sit at table tt.

The total score across the whole wedding is:

Htotal=tTHtH_{\mathrm{total}} = \sum_{t \in T} H_t

At a high level, this is what we want to maximise.

A good seating plan places compatible people together, avoids awkward pairings, and creates tables where conversation has a decent chance of surviving beyond the starter.

Mathematical structure

Sets and indices

  • GG: set of guests
  • TT: set of tables
  • CG×GC \subseteq G \times G: pairs of guests who must sit together
  • MG×GM \subseteq G \times G: pairs of guests who must not sit together

Parameters

ParameterDescription
wijw_{ij}relationship score between guests ii and jj
ctc_tcapacity of table tt
nntotal number of guests
mmtotal number of tables
kknumber of seats per table, when all tables are equally sized

The relationship score wijw_{ij} is the main modelling ingredient. It does not need to be perfect. In real life, it never will be. But it should capture the strongest signals: close friendships, family groups, guests who may feel isolated, and pairings best avoided.

Decision variables

We introduce binary assignment variables:

xit={1,if guest i is assigned to table t0,otherwisex_{it} = \begin{cases} 1, &\text{if guest } i \text{ is assigned to table } t \\ 0, &\text{otherwise} \end{cases}

We also introduce pair variables:

zijt={1,if guests i and j both sit at table t0,otherwisez_{ijt} = \begin{cases} 1, &\text{if guests } i \text{ and } j \text{ both sit at table } t \\ 0, &\text{otherwise} \end{cases}

The assignment variables say where each guest sits. The pair variables tell us which relationships become active because two guests are seated at the same table.

This lets us write the objective cleanly.

Objective function

The natural objective is:

maxtTiGjGj>iwijzijt\max \sum_{t \in T} \sum_{i \in G} \sum_{j \in G \mid j > i} w_{ij} z_{ijt}

In words: add up all the positive and negative relationship scores created by the seating arrangement, then choose the arrangement with the highest total score.

This is the core of the model.

It formalises the idea that a good seating plan is one that creates strong local social environments. Tables should feel cohesive. Guests should not spend the evening stranded between groups that do not quite fit together.

Constraints to keep the model realistic

The objective is only half the story. Wedding seating is shaped just as much by what cannot happen.

Each guest must sit somewhere. Tables have capacities. Some people must sit together. Some people must not. Every solution has to respect those rules, no matter how good the objective score looks.

A compact formulation is:

tTxit=1for all iGiGxit=ctfor all tTzijtxitfor all i,jG,  j>i,  tTzijtxjtfor all i,jG,  j>i,  tTzijtxit+xjt1for all i,jG,  j>i,  tTxit+xjt1for all (i,j)M,  tTxitxjt=0for all (i,j)C,  tT\begin{alignat}{2} \sum_{t \in T} x_{it} = 1 &\quad \text{for all } i \in G \\ \sum_{i \in G} x_{it} = c_t &\quad \text{for all } t \in T \\ z_{ijt} \le x_{it} &\quad \text{for all } i,j \in G,\; j > i,\; t \in T \\ z_{ijt} \le x_{jt} &\quad \text{for all } i,j \in G,\; j > i,\; t \in T \\ z_{ijt} \ge x_{it} + x_{jt} - 1 &\quad \text{for all } i,j \in G,\; j > i,\; t \in T \\ x_{it} + x_{jt} \le 1 &\quad \text{for all } (i,j) \in M,\; t \in T \\ x_{it} - x_{jt} = 0 &\quad \text{for all } (i,j) \in C,\; t \in T \end{alignat}

These constraints do a few things:

  • assign each guest to exactly one table
  • fill each table to its required capacity
  • link the pair variable zijtz_{ijt} to the assignment variables
  • prevent pairs in MM from sitting together
  • force pairs in CC to sit together

This is already enough to make the problem interesting. We are balancing compatibility, capacity, exclusions, and forced groupings all at once.

And real weddings usually add softer preferences too:

  • these relatives should be near the front
  • this table should probably be a bit younger
  • these two groups might mix well
  • this guest should not feel like an afterthought
  • this table needs at least one person who can carry a conversation

Some of these can be modelled. Some are better left to judgement. That is part of the charm and the pain.

Why this gets hard quickly

At first, the problem looks manageable. You have some guests, some tables, and a scoring system. How bad can it be?

Quite bad, mathematically speaking.

Even if we ignored relationships entirely and only asked how many ways there are to split nn guests into mm tables of size kk, the number of possible arrangements is:

n!(k!)mm!where m=nk\frac{n!}{(k!)^{m} \cdot m!} \quad \text{where } m = \frac{n}{k}

This grows ridiculously fast. A seating plan that looks small to a human can already be far too large to check by brute force.

The real difficulty is that moving one guest does not change one thing. It changes their relationship with everyone at the table they leave, and everyone at the table they join. One swap can improve one table, damage another, and create a new problem somewhere else.

That is why seating plans feel so slippery. The quality of a table depends on combinations of people, not isolated guests.

Structurally, this sits close to graph partitioning and clique-style grouping problems. We are trying to divide a weighted graph into groups of fixed size, where the internal edge weights are as strong as possible. Problems in this family are hard in general, and wedding seating inherits that difficulty.

So if you have ever stared at a seating spreadsheet for two hours and felt your soul slowly leaving your body, there is some comfort: it is not just you. The problem is genuinely difficult.

Exact optimisation versus practical solving

For small weddings, exact optimisation is possible. You can give the model to an integer programming solver and ask it to find the best arrangement under your assumptions.

That is satisfying because it gives you the true optimum for the model you built.

But for larger weddings, exact optimisation can become slow. There are many binary decisions, many pairwise interactions, and a search space that grows quickly. The solver has to explore a large number of possible assignments while respecting all the social constraints.

That is where heuristics become useful.

This is also where the maths starts to resemble what people naturally do anyway:

  • start with obvious groups
  • lock in the non-negotiables
  • draft an initial seating plan
  • swap people around
  • check the tables again
  • repeat until nothing looks obviously terrible

That is basically local search. It may not sound glamorous, but it is a recognisable optimisation method: start with a solution, make small changes, keep the changes that improve the result, and stop when further improvements become hard to find.

You can make this more formal with greedy construction, hill climbing, local swaps, simulated annealing, or integer programming relaxations. The point is not that one approach solves every wedding perfectly. The point is that once you see the structure of the problem, you can choose tools deliberately instead of just dragging names around a spreadsheet and hoping for divine intervention.

A tiny brute-force example

Here is a toy example with four guests and two tables. This is obviously not how you would solve a real wedding, but it makes the mechanics clear.

import itertools

guests = ["A", "B", "C", "D"]

w = {
  ("A", "B"): 5,
  ("A", "C"): 7,
  ("A", "D"): -5,
  ("B", "C"): 3,
  ("B", "D"): -5,
  ("C", "D"): -3
}

def pair_score(i, j):
  return w.get((i, j), w.get((j, i), 0))

def table_score(table):
  total = 0
  for i, j in itertools.combinations(table, 2):
      total += pair_score(i, j)
  return total

best_assignment = None
best_score = float("-inf")

for table_1 in itertools.combinations(guests, 2):
  table_2 = tuple(g for g in guests if g not in table_1)

  score = table_score(table_1) + table_score(table_2)

  if score > best_score:
      best_score = score
      best_assignment = (table_1, table_2)

print(best_assignment, best_score)

This brute-force search checks all possible arrangements and keeps the best one. It works here because the instance is tiny. Its value is explanatory, not practical.

It shows the basic logic: generate a seating plan, score the relationships created by that plan, and keep the best result.

For a real wedding, brute force quickly becomes impossible. But the same scoring idea can still guide more practical methods.

When total happiness is not enough

Maximising total happiness is a reasonable objective, but weddings are not just about the total.

A seating plan where seven tables are excellent and one table is miserable may score well overall, but it still feels wrong. Nobody wants to be the sacrificial table in someone else’s optimisation model.

That suggests a different objective: maximise the score of the weakest table.

One way to write that is:

max  ηsubject to ηHt  for all tT\max \; \eta \quad \text{subject to } \eta \le H_t \; \text{for all } t \in T

Here, η\eta represents the minimum table score. Instead of only chasing the highest total score, the model tries to lift the floor.

I like this version because it captures something very real. A wedding is closer to a “no bad tables” problem than a pure “maximise average happiness” problem.

This is one of the most useful things optimisation can do. It does not just give us an answer. It forces us to ask what kind of answer we actually want.

Do we want the highest total score?
The fewest awkward pairings?
The most balanced room?
The best experience for the most isolated guests?

Those are different objectives. Different objectives can lead to different seating plans.

Final thoughts

A wedding seating chart is a combinatorial optimisation problem hiding inside a social ritual.

Underneath the spreadsheet, there is graph structure, pairwise compatibility, table capacity, hard constraints, soft preferences, fairness, and a search space that becomes unmanageable with surprising speed.

That is why the task is difficult. It is not just a matter of being organised. The problem itself is deeply interconnected.

But that is also what makes it interesting.

The model does not replace human judgement. It gives judgement a better structure. It helps turn vague concerns into compatibility scores, constraints, and trade-offs. It shows why some arrangements feel better than others, and why one innocent-looking swap can ruin a table.

In the end, the goal is not to find some abstract mathematical optimum.

The goal is to create a room where people feel comfortable, included, and able to enjoy themselves without silently wondering why they were placed between two strangers discussing mortgage rates.

That may not sound like traditional operations research.

But it is exactly the kind of problem operations research is good at: taking a messy human system, making the trade-offs visible, and finding a better way through.

Sometimes that means optimising power grids or bike-sharing systems.

And sometimes it means saving the wedding breakfast from becoming a poorly clustered graph.




Share article