Design a Task Board (Jira / Trello)

Tools like Trello and Jira let a team organize work on a board: columns like “To Do,” “In Progress,” and “Done,” with cards you drag between them. The interesting part for system design is collaboration: when your teammate drags a card, you see it move on your screen a second later, without refreshing. Let’s design a task board that does this.

🎯 What the System Does

A task board needs to:

  • Let users create boards, lists (the columns), and cards (the tasks).
  • Let users move cards between lists and reorder them.
  • Show changes to everyone on the board live, without a refresh.
  • Let several people edit at once without chaos.

The data model is simple. The collaboration is where the real design thinking goes.

📋 Requirements

Functional (what it must do):

  • Create and edit boards, lists, and cards.
  • Move and reorder cards.
  • See other people’s changes in real time.

Non-functional (how well it must do it):

  • Live feel: updates should reach others within about a second.
  • Consistency: everyone should end up seeing the same board.
  • Scale: support many boards and many users per board.

🗂️ The Data Model

Let’s get the structure clear first, because everything builds on it. It’s a simple nesting:

  • A board belongs to a team.
  • A board has many lists (the columns), in a left-to-right order.
  • A list has many cards (the tasks), in a top-to-bottom order.

So it’s boards contain lists contain cards. The tricky bit is keeping the order of cards right, which we’ll tackle next.

🔢 Keeping Card Order

Here’s a subtle problem. Cards in a list have an order, and people drag them around constantly. How do you store “this card is third”?

The naive way is to number cards 1, 2, 3, 4. But then dragging a card to the top means renumbering every card below it. That’s a lot of updates for one little drag.

A smarter way is to give each card a position value with gaps, like 100, 200, 300. To drop a card between 100 and 200, you give it 150. No other card has to change.

Cards: A=100 B=200 C=300
Drag a new card between A and B:
A=100 NEW=150 B=200 C=300
# Only the new card got a position. Nothing else moved.

Reading that: leaving gaps between positions means inserting a card only sets that one card’s position. You rarely have to touch the others. That makes drag-and-drop cheap.

📡 Real-Time Updates

This is the heart of a collaborative board. When your teammate moves a card, how does it appear on your screen right away?

We use a live connection. The natural fit is WebSockets, a two-way connection that stays open:

  • Everyone viewing a board keeps an open connection to the server.
  • When someone makes a change, they send it to the server.
  • The server saves it, then pushes the update out to everyone else viewing that board.
  • Their screens update instantly, no refresh needed.

You move a card

Server saves the change

Push update to everyone on this board

Teammate 1 screen updates

Teammate 2 screen updates

Updates only go to people on that board

The server doesn’t tell the whole world about every change. It only pushes a board’s updates to the people currently viewing that board. This keeps the live updates focused and cheap.

🤝 Handling Edits at the Same Time

What if two people change the same card at once? For a task board, the stakes are low, so a simple rule usually works: last write wins. The most recent change is the one that sticks, and since everyone gets the live update, they all quickly settle on the same final state.

  • Most edits are on different cards, so real clashes are rare.
  • When two edits do hit the same card, the later one wins and everyone sees it. Good enough for a task board.

This is different from the auction or ticketing systems, where a clash means real money. There, you need strict safety. For a task board, simple and live beats strict and slow.

🏗️ High-Level Design

Putting it together.

Clients (browsers)

WebSocket / Realtime Service

Board Service

boards, lists, cards

Database

Cache

(fast board reads)

Reading the parts:

  • The realtime service holds the open connections and pushes live updates.
  • The board service handles the actual changes to boards, lists, and cards.
  • The database stores everything permanently.
  • A cache serves a board’s data fast when someone opens it.

📈 Scaling

To support many boards and teams:

  • Reading a board is the most common action, so cache board data for quick loads.
  • Split (shard) data by board, so each board’s data is a manageable piece.
  • The realtime service can run on many servers, each handling the connections for some boards.

🧰 Tech Choices

Part of system design is not just naming pieces, it’s saying why you picked each one. Here are the main technology decisions for this system and the reason behind each.

Decision Choice Why
Live collaboration WebSockets A two-way open connection pushes changes instantly, no refresh.
Keep card order Position values with gaps Dragging a card sets only that card’s position, no renumbering.
Store boards Database + cache Durable store, with a cache for fast board loads.
Clashing edits Last-write-wins Simple and good enough for low-stakes edits, unlike money systems.

⚠️ Common Mistakes and Misconceptions

A few things to keep straight:

  • “Number cards 1, 2, 3 and renumber on every move.” That’s a lot of updates per drag. Use position values with gaps so inserting a card touches only that card.
  • “Poll the server every few seconds for changes.” That’s wasteful and feels laggy. A live WebSocket connection pushes changes instantly.
  • “Use heavy locking so two edits never clash.” Overkill for a task board. Last-write-wins plus live updates is simple and good enough here.

🧩 What You’ve Learned

Nice work. Here’s the recap:

  • ✅ A task board nests boards, lists (columns), and cards (tasks).
  • ✅ Card order is stored as position values with gaps (100, 200, 300), so inserting a card touches only that card.
  • ✅ Real-time updates use an open WebSocket connection: a change is saved, then pushed to everyone viewing that board.
  • ✅ The server only pushes a board’s updates to people viewing that board, keeping it focused.
  • ✅ For clashing edits, last-write-wins is simple and good enough, unlike money systems that need strict safety.

Check Your Knowledge

Test what you learned. Pick an answer for each question, then click Check.

  1. 1

    Why store card order as position values with gaps (like 100, 200, 300)?

    Why: Gaps let you drop a card at, say, 150 between 100 and 200 without touching any other card. That makes drag-and-drop cheap.

  2. 2

    How do live updates reach other users on the board?

    Why: Everyone keeps an open connection. When one person changes something, the server saves it and pushes the update to the others instantly.

  3. 3

    Who gets a board's live updates?

    Why: The server pushes a board's updates only to users viewing that board, which keeps real-time updates focused and cheap.

  4. 4

    How does a task board usually handle two edits to the same card at once?

    Why: For a task board the stakes are low, so last-write-wins is fine. Strict locking is saved for money systems like auctions or ticketing.

🛠️ Design Challenge

Try extending the task board yourself. Think each one through first, then open the answer to see a full breakdown.

Offline edits. A user loses internet, moves a few cards anyway, then reconnects. How do you sync their changes?

Activity history. Show “who moved what and when” on a board. How would you build that?

Board permissions. Only team members should see or edit a board, with roles like admin, member, and viewer. How do you enforce that?

🚀 What’s Next?

You’ve designed a collaborative board. Let’s explore the pieces that power it.

Get these down and you’ll be ready for any live, collaborative app.

Share & Connect