Design Google Meet's Participant Grid - Vinit Shahdeo
Design Google Meet's Participant Grid
Keeping participant tiles stable as people join and leave, from the core data structure to scaling for thousands.
Vinit Shahdeo
Jun 22, 2026
I’ve asked this question in interviews a lot, and it’s still my favorite.
When people hear “video conferencing app,” they reach for the big stuff: databases, queues, services calling each other. You don’t need any of that. Most of this problem is just picking the right data structure. There’s a system design part too, but it only shows up if you keep going, so I’ve put it at the end. Let’s start with the core.
Here’s the question:
Design the participant layout for a video conferencing app like Google Meet. People join and leave at any time. Show everyone in a grid.
One thing to clear up first, because someone always brings it up. Real apps like Meet and Zoom don’t show a plain grid. They deal with the active speaker, pinned people, screen sharing, and so on, and they don’t move tiles around the way I’m about to describe. For this question, just assume everyone has equal priority, and the only goal is to keep tiles from jumping around. That version is the interesting one.
Understand the problem first
Spend a minute here. Half the people I talk to skip this and wish they hadn’t.
What does it need to do?
- People join and leave whenever.
- Everyone shows up in a grid.
And the one rule that makes it hard:
- A tile that’s already on screen shouldn’t move when someone joins or leaves.
If you’ve been on a call where everyone slides over the second someone joins, you know why this matters. That one rule is basically the whole problem.
The first answer almost everyone gives
Nine out of ten people start here:
“Keep everyone in a list, sort by name or join time, and draw the grid from that.”
It looks fine with three people. So I ask:
“What happens when the second person leaves?”
And it breaks:
- Someone joins in the middle, and everyone after them shifts down a spot.
- Someone leaves, and you either get a hole or everyone slides over to close it.
Either way, tiles jump. The problem isn’t the architecture. It’s that the data structure is wrong for the rule we set. Sorting rebuilds the whole layout from scratch every time anything changes, and a video call is nothing but a change.
The key insight: name the rule first
Every problem like this has one rule that everything hangs on. Here it is:
Tiles that are already on screen shouldn’t move.
Once you say that out loud, the data structure picks itself. A sorted list breaks the rule on purpose, because it works out every position again on every change. So don’t sort.
Instead, give each person a seat when they show up: the next open seat, at the end. That seat is theirs. When someone new joins, a tile appears at the end, and nobody else moves.
The leave is the real question
Joins are easy. Leaves are where it gets interesting.
Say the grid is A(0) B(1) C(2) D(3) and B leaves from the middle. You have three choices:
- Shift everyone up. C moves to 1, D moves to 2. Every tile after the gap moves. Same bug as before. Don’t.
- Leave the hole. Nobody moves, but now there’s an empty spot in the middle until someone joins. On a small call where you can see everyone, it just looks broken.
- Move the last person into the gap. Take D from the end and drop them into B’s spot. Now it’s
A(0) D(1) C(2). One tile moves, the grid stays full, and the empty space ends up at the back.
The third one is the answer, and it’s not a toss-up. If you want the grid to stay full after someone leaves from the middle, something has to move. You can’t fill a hole without moving at least one tile, and moving the last person is the smallest move you can make. It’s the old swap-with-last trick.
When someone gets here on their own, I know the rest of the conversation is going to be good.
The data structure
Since you always fill gaps from the end, you never build up holes, so the whole thing stays small. An array and one map:
const order = []; // index = seat, value = person
const seatOf = new Map(); // person -> seat index
function join(person) {
seatOf.set(person, order.length);
order.push(person); // new tile at the end; nobody moves
}
function leave(person) {
const i = seatOf.get(person);
seatOf.delete(person);
const last = order.pop(); // the person in the last seat
if (i < order.length) { // the leaver wasn't already last
order[i] = last; // move them into the gap
seatOf.set(last, i); // one tile moves
}
}
Both join and leave are O(1). That’s the core.
Putting it together
What I’m listening for
- Good sign: they spot the “don’t move existing tiles” rule before writing any code, and they land on moving the last person to fill a gap.
- Bad sign: they re-sort on every change and never notice the tiles jumping.
And the follow-up I always ask:
“Someone in the middle leaves. What happens to everyone else?”
Three answers, three signals:
- “Everyone shifts up.” That’s the cascade. Back to start.
- “Leave the seat empty until someone joins.” Works, but now there’s a hole in the middle.
- “Move the last person into the gap; everyone else stays put.” Good answer.
That’s the whole question, really. One idea, used well. If you can explain the seat trick to a friend in two minutes, you’ve got it.
Going further: the system design part
If the interview keeps going, this is where the small problem turns into a system. Three things come up, more or less in this order. The seat idea doesn’t change. This is all built around it.
1. Everyone has to agree on the order. Seat assignment depends on the order in which things happen. Append-on-join and move-the-last-on-leave yield different results if two machines observe the joins and leaves in a different order. So you can’t let every client work out their own seats; they’d disagree. You need one referee. One server per meeting hands out seats and tells everyone. That’s your single source of truth. It’s cheap, because joins and leaves don’t happen often. Even a 1,000-person meeting has only a few per second. The heavy traffic is the video itself, handled by a separate part of the system (the SFU). Working out seat order is tiny next to that, and you’d never put it in the video path.
2. Keeping everyone in sync. Send the full picture once, then small updates after that. Same idea as video: one keyframe, then diffs. When you join, the server sends the full seat map. After that, it sends small updates with a number on each one: “Vinit joined at seat 7,” “Vaibhaw moved from seat 12 to seat 3.” The rule on the client is to apply them strictly in order: skip anything you’ve already seen, ask for a fresh snapshot if you spot a gap, and otherwise apply the next one. That makes repeats and out-of-order messages harmless.
// Update #42: move Vaibhaw from seat 12 to seat 3
if (update.version <= appliedVersion) return; // dup or stale, ignore it
if (update.version > appliedVersion + 1) { // gap, we missed one
requestSnapshot(); // resync instead of guessing
return;
}
apply(update); // exactly the next one
appliedVersion = update.version;
And if the network drops, don’t blank the screen. Keep showing the last layout, reconnect, then fix only what changed.
3. Scaling to thousands. You can’t draw 3,000 tiles or pull 3,000 video streams. So show one page at a time, around 50 tiles, and only pull video for the people on that page, plus the speaker and anyone you’ve pinned. The seat map can hold thousands of people in memory cheaply. You just draw a small slice of it.
This keeps leaves cheap, but be clear about why. A leave changes exactly one seat: the gap gets the last person, and the very last seat goes away. So on whatever page you’re looking at, at most one tile changes. A new face shows up in the gap, and everyone else stays put. The person who got moved was probably on another page anyway, so it feels like a new face showing up, not the grid shuffling. Most of what you see stays still, because only one seat ever changes.
A quick note on “thousands”: one server can hand out seats, but it can’t carry all the video. At that size, the media is spread across several SFUs that work together (cascading), and most people are view-only. None of that touches the seat logic, though. The layout side stays the same.
No matter how big the meeting gets, you’re drawing around 50 and pulling around 50.
The question I save for the best people
Once someone has all of it, I ask one more:
“What if we wanted nothing on screen to move at all, not even the one tile?”
There’s no single clean answer, which is why I like it. It opens up a bunch of options: leave the holes and live with them; only tidy up the grid every so often, in a batch, so movement is rare; keep seat numbers separate from the actual screen spots; give each tile a fixed ID so it slides into place instead of snapping; or only tidy up the part of the grid you can actually see. Listening to someone think through those trade-offs is what tells me good from great.
That’s why I keep asking it. Take away the video call, and the real lesson is the part I care about: figure out the one rule first, then pick the data structure that keeps it with the least work. Here, the rule was “don’t move tiles,” and once you name it, an array and a swap-with-last do the whole job. No fancy algorithms. The core shows me how someone thinks. The scaling part shows me how far they can go.
If you liked this, you'll probably like another question I keep asking in interviews. 99% of people fail to answer it: Re-Hash Facebook’s Passwords **.