Designing a Real-Time Chat Routing System for Customer Support

Designing a Real-Time Chat Routing System for Customer Support

Designing a Real-Time Chat Routing System for Customer Support

A production-grade walkthrough of matching incoming support conversations to the best available agent, in real time, at scale — the way it is built at companies like Zendesk, Intercom, Salesforce Service Cloud, and Amazon Connect. Queueing theory, fairness, exactly-once assignment, and graceful degradation, all under a sub-second latency budget.

01

Introduction & History

Every time you open a chat widget on a company’s website and type “I need help with my order,” something has to decide, within a second or two, which human being on the other side should answer you. That decision — invisible to the customer — is one of the more interesting real-time matching problems in software: it blends queueing theory, real-time systems, and a surprising amount of business logic (skills, priority tiers, language, SLAs) into a single split-second routing decision, repeated potentially thousands of times per minute across a large support organization.

This tutorial designs that system: a platform that accepts incoming chat conversations from customers and routes each one, in real time, to the most appropriate available support agent, at scale, without dropping conversations, and while respecting service-level agreements (SLAs) and fairness across the agent workforce.

1.1 A brief history

1

1990s — Call center ACD

The direct ancestor of chat routing. Phone-based Automatic Call Distributors routed inbound calls to available agents using simple round-robin or longest-idle-agent rules — the foundational ideas (queues, agent states, skill groups) all originate here.

2

Early 2000s — Live chat widgets emerge

Tools like LivePerson brought text-based chat to websites, initially with very simple routing — often just “first available agent” — because chat volumes were still low.

3

2007 – 2010 — Skill-based routing matures

As support organizations grew specialized teams (billing, technical, sales), routing engines started matching conversation attributes to agent skills rather than treating all agents as interchangeable.

4

2011 – 2015 — SaaS helpdesks

Zendesk, Intercom, and Freshdesk popularized SaaS helpdesk platforms. Chat became a first-class support channel alongside email and tickets, and routing logic became a configurable product feature rather than a bespoke call-center build.

5

2016 – 2019 — Omnichannel routing

Support organizations wanted a single queue and a single routing brain across chat, email, social media, and voice, rather than separate silos per channel — this is when routing engines evolved from “chat-specific” to genuinely channel-agnostic.

6

2018 – present — Bots and AI-assisted routing

Chatbots and AI deflect a portion of conversations entirely (self-service resolution) and, for the conversations that do reach a human, machine-learning models increasingly assist prioritization (predicting conversation urgency, likely resolution time, or churn risk) and agent-conversation fit.

7

2023 – present — LLM-based triage and co-pilot routing

Large language models now commonly perform initial intent classification and summarization before a conversation is routed, feeding richer signals (not just a customer-selected topic dropdown) into the routing decision.

💬
What the interviewer may ask

“Why is chat routing harder than round-robin ticket assignment?” Chat is synchronous and real-time — a customer is actively waiting, agent availability changes second-to-second (not just per shift), and a bad match (wrong skill, over-capacity agent) is immediately visible to a live human rather than silently sitting in an email queue.

1.2 Functional and non-functional requirements

Functional requirements

  • Accept incoming chat conversations from multiple channels (website widget, mobile app, social messaging) into a unified queue.
  • Track real-time agent state: online / offline, available / busy, current active conversation count, skills, languages.
  • Route each incoming conversation to the single best available agent based on skill match, workload, priority, and business rules.
  • Support priority tiers (for example, enterprise customers, SLA-bound conversations) that can jump the queue.
  • Support agent capacity limits (max concurrent chats per agent) and overflow or escalation when no agent is available.
  • Allow supervisors to monitor queue depth, wait times, and manually reassign conversations.
  • Preserve conversation context (prior messages, customer history) across any reassignment or transfer.

Non-functional requirements

  • Latency: routing decisions should complete in well under a second from conversation creation to agent assignment under normal load, since customers are actively watching a “connecting you to an agent” indicator.
  • Consistency: exactly one agent must be assigned per conversation — double-assignment (two agents both told they own the same chat) is a severe correctness bug, not just a UX annoyance.
  • Fairness: workload should be distributed reasonably evenly across agents with equivalent skills, both for morale and for contractual or labor reasons in many support organizations.
  • Availability: the routing system is on the critical path for every single incoming conversation — an outage means customers wait indefinitely or conversations silently vanish, so this needs very high uptime.
  • Scalability: must handle large spikes in conversation volume (for example, an outage-driven support surge) without routing decisions degrading in latency or quality.

1.3 Why this problem resembles (and differs from) other matching systems

If you have studied ride-hailing dispatch (matching riders to drivers) or job-scheduling systems (matching tasks to workers), chat routing will feel familiar — it is the same underlying shape: a stream of “requests” and a pool of “resources,” matched continuously under changing conditions. But a few differences matter enormously for the design:

  • The wait is visible and synchronous. A ride-hailing rider tolerates a few minutes of wait somewhat passively; a chat customer is staring at a live typing indicator or a “connecting…” message, and even a 10 to 15 second delay feels long. This pushes the latency budget for routing decisions much tighter than many other matching systems.
  • Agents are stateful and finite over a shift, not per-trip. A driver finishes one ride and is immediately available for the next; a support agent typically juggles several concurrent conversations up to a configured capacity, so “availability” is a continuous, multi-valued quantity (how much headroom does this agent have right now) rather than a simple binary.
  • Match quality has a long tail of business rules. Ride matching optimizes largely on proximity and ETA; chat routing optimizes on skill, language, priority tier, contractual SLA, and sometimes relationship continuity — a much richer and more configurable scoring surface.
  • Conversations do not “complete” cleanly. A ride has an unambiguous end (drop-off); a support conversation might be transferred, escalated, reopened by the customer hours later, or merged with a related ticket — the lifecycle model has to accommodate much messier real-world flows.

1.4 Back-of-the-envelope capacity estimation

Sizing the problem before designing the solution, as you would in an interview: assume a large support organization handling on the order of tens of thousands of concurrent live chat conversations across its whole platform at peak, spread across many customer accounts and many agent teams. If a typical agent can competently manage 3 to 6 concurrent conversations, and peak concurrent conversations run into the tens of thousands, the agent workforce needed at peak is naturally in the low thousands — meaning the presence and capacity service needs to track state for thousands of agents with sub-second freshness, while the routing engine needs to process new-conversation and capacity-freed events fast enough to keep the queue-depth-to-resolution ratio stable rather than growing unbounded during sustained peak load. This estimation is what justifies partitioning the matching problem by skill or queue group early in the design, rather than treating it as one giant global matching computation.

<1 sec
Target routing latency from queue to agent
3 – 6
Concurrent conversations a typical agent can handle
Exactly 1
Agent assigned per conversation — non-negotiable
02

Architecture & Components

At its core, this system is a real-time matching problem between two dynamic, fast-changing sets: incoming conversations (each with attributes like topic, priority, language, customer tier) and available agents (each with attributes like skills, current load, shift status). The architecture needs to keep both sides’ state fresh to the second and make a matching decision fast enough that the customer never notices a routing delay.

graph TB
    subgraph CLIENTS["Customer Facing Clients"]
        WEB["Web Chat Widget"]
        MOB["Mobile App"]
        SOC["Social Messaging Channels"]
    end

    subgraph INGRESS["Ingress Layer"]
        LB["Load Balancer"]
        GW["Conversation Gateway WebSocket"]
    end

    subgraph CORE["Routing Core"]
        INTAKE["Conversation Intake Service"]
        CLASSIFY["Intent Classification Service"]
        QUEUE["Priority Queue Manager"]
        ROUTER["Routing Engine"]
        PRESENCE["Agent Presence Service"]
    end

    subgraph AGENTSIDE["Agent Facing"]
        AGWEB["Agent Desktop Console"]
        AGWS["Agent WebSocket Gateway"]
    end

    subgraph DATA["Data Layer"]
        DB["Conversation Ticket DB"]
        CACHE["Redis Agent State"]
        STREAM["Event Stream Kafka"]
        CRM["Customer Profile CRM"]
    end

    WEB --> LB
    MOB --> LB
    SOC --> LB
    LB --> GW
    GW --> INTAKE
    INTAKE --> CLASSIFY
    CLASSIFY --> QUEUE
    QUEUE --> ROUTER
    ROUTER --> PRESENCE
    PRESENCE --> CACHE
    ROUTER --> AGWS
    AGWS --> AGWEB
    AGWEB --> PRESENCE
    INTAKE --> DB
    CLASSIFY --> CRM
    ROUTER --> STREAM
    STREAM --> DB
Figure 2.1 — End-to-end architecture. Customer-facing intake feeds a routing core backed by fast presence state and durable event logging.

2.1 Core components

Ingress

Conversation Gateway

Terminates customer-facing WebSocket or long-poll connections; normalizes messages from different channels into a common internal event format.

Intake

Conversation Intake Service

Creates a conversation record, attaches customer identity and context, and hands it off for classification and queueing.

Classify

Intent Classification Service

Analyzes the initial message (and customer metadata) to infer topic, urgency, and required skill — using rules, ML models, or increasingly an LLM-based classifier.

Queue

Priority Queue Manager

Holds waiting conversations ordered by priority and wait time, partitioned by skill or queue group.

Router

Routing Engine

The matching brain — continuously (or event-driven) pairs waiting conversations with available agents based on configured strategy.

Presence

Agent Presence Service

Tracks real-time agent state: online, away, or offline; current active conversation count; skills; and capacity limits.

Agent UI

Agent Desktop / Console

The UI agents use to receive assigned conversations, see customer context, and update their own status or capacity.

Async

Event Stream

Durable log of all conversation lifecycle events (created, queued, assigned, transferred, closed) — feeds analytics, SLA tracking, and downstream systems.

Production example

Zendesk’s “Omnichannel Routing” maintains a real-time capacity model per agent (a configurable max number of concurrent conversations, often weighted differently per channel — for example, a chat “costs” more capacity than an email) and continuously assigns from a prioritized queue as capacity frees up, rather than only routing at the moment a new conversation arrives.

2.2 Component deep dive: the Intent Classification Service

Classification quality directly determines routing quality — if the system mis-tags a billing question as “general inquiry,” the customer lands in the wrong skill queue and the eventual agent has to redirect them, wasting everyone’s time. Modern implementations layer several signals:

  • Explicit customer input: a topic dropdown, a pre-chat form, or a bot’s initial “what can I help you with” flow — the most reliable signal when available, but not always present (many customers skip optional forms).
  • Lightweight rules or keyword matching: fast, deterministic, cheap — good for high-confidence, clearly-worded requests (“cancel my subscription” almost always means billing or account).
  • ML classifier or LLM-based intent extraction: handles ambiguous or multi-topic messages better than rules, at the cost of added latency and compute — often used as a fallback when rules produce low confidence, rather than for every single message.
  • Customer / account metadata: account tier, product line, past conversation history — used to bias classification and set priority even before the message content is fully analyzed.

A key architectural decision is that classification must have a bounded worst-case latency with a safe default: if the classifier is slow, times out, or is simply unavailable, the conversation should fall into a general or default queue rather than block indefinitely waiting for a perfect classification.

2.3 Component deep dive: the Agent Presence Service

This service is deceptively small in scope but sits directly in the hottest path of the entire system — nearly every routing decision reads from it, and every agent status change writes to it. It typically maintains, per agent: online / away / offline status, current active conversation count, configured max capacity (often per-channel-weighted), skill and language tags, and a last-heartbeat timestamp used to detect silent disconnects. Because this data is read far more often than it is written (many routing evaluations per status change), it benefits from being served out of an in-memory store with read replicas, and from being denormalized or pre-filtered by skill group so the routing engine does not need to scan the entire agent population for every match attempt.

03

Internal Working — The Routing Engine

The routing engine is the component worth understanding most deeply, because “route to the most appropriate available agent” is deceptively simple to state and genuinely hard to implement correctly at scale.

3.1 The core matching loop

Conceptually, the routing engine runs (or reacts to) a matching loop with two possible triggers:

  1. Event-driven trigger: a new conversation arrives, or an agent becomes available (finishes a chat, comes online) — either event should immediately attempt a match rather than waiting for a fixed polling interval, since polling introduces unnecessary latency into a customer-facing wait time.
  2. Periodic reconciliation: a lower-frequency background pass (for example, every few seconds) re-evaluates the queue to catch any conversations that should have been matched but were not — for instance, due to a race condition or a transient failure in the event-driven path. This acts as a safety net, not the primary mechanism.

3.2 Avoiding double-assignment: the concurrency problem

The single most important correctness property in this system is that exactly one agent gets assigned to a given conversation, even when multiple routing attempts might be evaluating the same agent’s availability simultaneously (for example, two conversations both eligible for the same lone available specialist). This is a classic distributed concurrency problem, and it is solved the same way most “exactly one winner” problems are solved:

  • Atomic compare-and-swap on agent capacity: when the router decides to assign a conversation to an agent, it performs an atomic decrement of that agent’s remaining capacity (for example, a Redis DECR with a floor check, or a conditional update) — if the decrement would take capacity below zero, the assignment is rejected and the router retries with the next-best agent.
  • Single-writer partitioning: an alternative approach partitions agents (and their queues) across router instances by consistent hashing, so only one router instance ever makes assignment decisions for a given agent, eliminating the race entirely rather than resolving it after the fact.
💬
What the interviewer may ask

“How do you prevent two conversations from both being assigned to the same agent’s last open slot?” This is one of the most common follow-up questions for this design. The strongest answers describe an atomic, single-source-of-truth capacity check (not “read capacity, then separately write assignment,” which is a classic check-then-act race condition) — either via an atomic data-store operation or by architecturally guaranteeing only one process ever decides for a given agent.

3.3 Scoring candidate agents

For a given waiting conversation, the router typically narrows the agent pool to those matching hard requirements (skill, language, currently online, has capacity) and then scores the remaining candidates to pick the best one:

Scoring FactorWhy It Matters
Skill match qualityAn agent whose primary skill exactly matches the conversation’s classified topic is preferred over one with only a secondary or tangential skill.
Current load relative to capacityPrefer agents further from their max capacity, to spread load evenly and avoid overloading any one agent.
Idle time / longest-availableAmong otherwise-equal candidates, prefer the agent who has been idle longest — this is the direct descendant of call-center “longest idle agent” routing and promotes fairness.
Historical performance / fitSome systems factor in an agent’s historical resolution time or satisfaction score for similar conversation types — a data-driven refinement layered on top of the base rules.
Customer relationship continuityIf the customer has an existing relationship with a specific agent (for example, an assigned account manager), that agent may be preferred even outside strict availability rules, subject to business configuration.

3.4 Handling multi-skill agents and overlapping queues

Most real support agents have more than one skill (for example, billing and general account questions), which means the clean picture of “one skill group, one isolated queue” breaks down in practice — an agent might be eligible for several queues simultaneously, and the routing engine needs a policy for which queue’s conversation they receive when both have waiting work. A common approach assigns each agent a primary and secondary skill ranking, and the router prefers matching them against their primary-skill queue first, only pulling from secondary queues when the primary queue is empty or the agent has been idle beyond a threshold — this keeps specialist capacity available for the work it is best suited for while still preventing agents from sitting idle when cross-trained work exists.

3.5 Conversation batching under extreme load

Under normal load, event-driven one-at-a-time matching (a conversation arrives, immediately find its best agent) is optimal for latency. But under extreme burst conditions — many conversations and many available agents changing state within the same few hundred milliseconds — naive one-at-a-time matching can produce suboptimal global assignments (for example, greedily assigning an okay-fit agent to conversation A when a slightly-later-arriving conversation B would have been a much better fit for that same agent). Some systems address this with brief micro-batching: collect events within a small window (on the order of 100 to 300 ms), then run a bipartite matching optimization (for example, a variant of the Hungarian algorithm or a simpler greedy-by-best-score approach) across the whole batch at once. This trades a small, bounded amount of added latency for meaningfully better aggregate match quality during bursts — a classic example of relaxing a per-item optimization in favor of a batch-level one.

04

Data Flow & Lifecycle

Walking through a single conversation end to end — from first message to agent assignment — makes the coordination between intake, classification, queue, presence, and router concrete.

4.1 Conversation lifecycle

sequenceDiagram
    participant Cust as Customer
    participant GW as Conversation Gateway
    participant Intake as Intake Service
    participant Classify as Classification Service
    participant Q as Queue Manager
    participant Router as Routing Engine
    participant Presence as Agent Presence Service
    participant Agent as Agent Desktop

    Cust->>GW: Opens chat and sends first message
    GW->>Intake: Create conversation record
    Intake->>Classify: Classify intent urgency skill
    Classify->>Intake: topic billing priority standard
    Intake->>Q: Enqueue conversation skill billing
    Q->>Router: Notify new conversation queued
    Router->>Presence: Query available billing skilled agents
    Presence->>Router: Agent A idle 40 seconds Agent B idle 5 seconds
    Router->>Presence: Atomic capacity decrement Agent A
    Presence->>Router: Success
    Router->>Agent: Assign conversation to Agent A
    Router->>Cust: Notify connected to agent
    Agent->>Cust: Hi how can I help
Figure 4.1 — End-to-end flow from customer message to agent assignment, including the atomic capacity decrement that guarantees exactly-once assignment.

4.2 Agent status change lifecycle

Agent presence is just as real-time as conversation intake. When an agent finishes a chat, goes on break, or logs in for their shift, the presence service must reflect that change immediately, because a stale “available” status leads to a conversation being routed to someone who is not actually free (a serious failure mode), while a stale “busy” status wastes available capacity and inflates customer wait times unnecessarily.

  1. Agent action (closes chat, clicks “away,” logs out) is sent to the presence service via the agent WebSocket connection.
  2. Presence service updates the agent’s state in the shared cache (Redis) with a short TTL-based heartbeat, so a crashed or disconnected agent client is automatically marked unavailable after a brief timeout rather than staying “available” forever.
  3. A capacity-freed event triggers the router to immediately attempt to fill that newly available slot from the queue, rather than waiting for the next conversation-side event.

4.3 Transfer and escalation lifecycle

Conversations do not always end with the first-assigned agent — they get transferred to a specialist, escalated to a supervisor, or handed off at shift change. Each of these is modeled as a re-entry into the routing engine with the existing conversation’s full context (message history, customer profile, prior classification) attached, rather than as a brand-new conversation — this preserves continuity for the customer, who should not have to repeat themselves, and gives the newly assigned agent everything the router and previous agent already knew.

4.4 Handling reopened conversations

Customers frequently return to a conversation hours or days after it appeared “closed” — replying to a resolved chat, or a bot escalating a previously-deflected inquiry. The lifecycle model needs to distinguish a genuinely new conversation from a reopened one, because a reopened conversation typically carries strong affinity signal toward the previously-assigned agent (continuity matters for customer experience) and should not necessarily re-enter the general queue from scratch. A common pattern is a configurable reopen window (for example, 24 to 72 hours) during which a new customer message on a closed conversation is routed back to the original agent if they are still available, and falls back to standard routing only if that agent has left the team, is offline for an extended period, or the reopen window has expired.

05

Routing Algorithms & Matching Strategies

This is the heart of the system design — the actual algorithmic decision of who gets what.

5.1 Common routing strategies

StrategyHow It WorksBest For
Round RobinCycles through available agents in a fixed order regardless of skill or load nuance.Simple, homogeneous teams where every agent can handle any conversation equally well.
Longest Idle AgentAlways routes to whichever eligible agent has been available or idle the longest.Fairness-focused teams; naturally self-balances load over time without explicit load tracking.
Skill-Based RoutingFilters agents by required skill(s) or language before applying a secondary tiebreaker (for example, longest idle).Specialized support organizations with distinct topic areas (billing, technical, sales).
Load-Based / Capacity-WeightedScores agents by how much headroom they have relative to their configured max capacity, preferring the least-loaded eligible agent.Teams where agents can handle multiple concurrent chats and capacity varies per agent (for example, senior agents handle more).
Priority QueueingMaintains separate priority tiers (for example, enterprise SLA customers) that are served ahead of standard-tier conversations within the same skill group.Organizations with tiered support contracts or SLA commitments.
Affinity / RelationshipPrefers an agent who has previously interacted with this specific customer, when available.High-touch account-based support models.
Predictive / ML-AssistedUses a trained model to predict which agent is statistically likely to resolve this specific conversation fastest or most successfully, based on historical patterns.Large-scale operations with enough historical data to train reliable models; typically layered on top of, not instead of, the rule-based strategies above.
💬
What the interviewer may ask

“Would you use pure round robin or something smarter?” Pure round robin is rarely sufficient in production because it ignores skill fit and current load entirely — most real systems combine a hard skill or eligibility filter with a load-aware or longest-idle tiebreaker, and layer priority tiers on top for SLA-bound customers.

5.2 Priority queue design

The queue is not a single FIFO list — it needs to support multiple priority tiers while still preventing starvation of lower-priority conversations. A common approach is a weighted / aging priority queue: each conversation has a base priority (from its tier or SLA), but its effective priority increases the longer it waits, ensuring that even a standard-tier conversation eventually rises above a newly-arrived high-priority one if it has been waiting long enough — this prevents an organization’s lowest-priority customers from waiting indefinitely during sustained high-priority volume.

graph LR
    A["New Conversation"] --> B{"Priority Tier"}
    B -->|"Enterprise SLA"| C["High Priority Queue"]
    B -->|"Standard"| D["Standard Priority Queue"]
    B -->|"Low or Best Effort"| E["Low Priority Queue"]
    C --> F["Aging Function"]
    D --> F
    E --> F
    F --> G["Effective Priority Score"]
    G --> H["Router selects highest scoring eligible conversation per available agent"]
Figure 5.1 — A weighted priority queue with aging so long-waiting standard-tier conversations eventually outrank newly-arrived high-tier ones.

5.3 Handling no available agent — overflow strategies

Real-time routing must define explicit behavior for the case where no eligible agent is currently available — this is not an edge case, it is a routine operating condition during peak load:

Overflow

Queue with ETA

The customer sees an honest queue position and estimated wait, computed from current queue depth and historical resolution rates.

Overflow

Broader Skill Group

After a configurable wait threshold, relax the skill requirement (for example, allow any agent, not just billing specialists) rather than let the customer wait indefinitely for a narrow specialist pool.

Overflow

Secondary Team / Region

Route to a different regional or outsourced support team when the primary team is saturated.

Overflow

Bot / Self-Service

Offer an AI-assisted or self-service resolution path while waiting, which can fully resolve a meaningful fraction of conversations without ever reaching a human agent.

Overflow

Callback / Async

Convert the real-time chat into an asynchronous ticket with a promised response time, when live wait times exceed an acceptable threshold.

5.4 Fairness vs. optimality

There is a genuine tension between routing every conversation to the objectively “best” agent (by skill or performance score) and distributing workload fairly across the team. Always routing to the single top-scoring agent creates burnout risk and morale issues for your best performers while under-utilizing everyone else. Most production systems address this with a bounded randomization or weighted-lottery approach among the top-N eligible candidates, rather than a strict deterministic “always pick the single best match,” trading a small amount of theoretical optimality for sustainable workload distribution.

5.5 Worked example: scoring function

It helps to make the scoring step concrete. A typical composite score for a candidate agent, given a specific waiting conversation, might combine several normalized (0 to 1) sub-scores with configurable weights:

  • Skill match score (weight ~0.4): 1.0 for an exact primary-skill match, lower for secondary or tangential skill matches.
  • Capacity headroom score (weight ~0.3): proportion of unused capacity remaining, so agents further from their max load score higher.
  • Idle time score (weight ~0.2): normalized time since the agent’s last assignment, rewarding longer-idle agents.
  • Relationship / continuity score (weight ~0.1): a boost if this agent has prior history with the specific customer.

The router computes this composite score for every eligible candidate, then either picks the single highest-scoring agent or samples from among the top few candidates with probability proportional to score (the bounded-randomization approach discussed above). Making these weights configurable per team or queue, rather than hardcoded, is important in practice — a sales queue might weight relationship continuity much more heavily than a general billing queue would.

5.6 Bot deflection as part of the routing funnel

It is worth explicitly modeling bot or self-service resolution as the first stage of the overall routing funnel, not a separate system. A well-designed platform attempts automated resolution (FAQ matching, guided self-service flows, or an LLM-based assistant) before a conversation ever reaches the human-agent queue, and only escalates to human routing when the automated attempt fails, the customer explicitly requests a human, or the classified intent or priority indicates automated handling is inappropriate (for example, an angry or high-value customer). This meaningfully changes the capacity-planning math for the human-agent routing system, since a well-tuned bot layer can deflect a substantial fraction of total volume, leaving human routing to handle the harder, lower-volume remainder.

06

Advantages, Disadvantages & Trade-offs

Every meaningful design choice in this system is a bet on which trade-off is worth accepting. The table below captures the biggest ones.

DecisionAdvantageDisadvantage / Trade-off
Event-driven matching over pollingMinimal routing latency; agents and customers both see near-instant assignment.More complex to implement correctly (race conditions, exactly-once assignment) than a simple periodic batch job.
Skill-based routing over round robinBetter conversation-agent fit, higher first-contact resolution rates.Requires accurate skill tagging or classification, and risks narrow specialist pools becoming bottlenecks.
Aging priority queue over strict priorityPrevents starvation of lower-priority conversations.Adds complexity to queue scoring and requires careful tuning of aging rates.
Bounded randomization over strict best-matchSustainable, fair workload distribution across the team.Slightly lower average conversation-agent fit compared to always picking the theoretically optimal agent.
Single-writer partitioning for agent assignmentEliminates double-assignment races architecturally.Requires careful partition rebalancing when router instances scale up, down, or fail.

6.1 Trade-off: real-time matching vs. batch optimization

Pure event-driven, one-at-a-time matching minimizes per-conversation latency but can produce globally suboptimal assignments during bursts, as discussed earlier. Pure batch matching maximizes global assignment quality but adds latency to every single conversation, even during quiet periods when there is no real contention to optimize around. The right answer in production is almost always adaptive: default to immediate one-at-a-time matching, and only shift into short batching windows when queue depth or event rate crosses a threshold that suggests contention is actually happening — applying the more expensive optimization exactly when it earns its cost, and not before.

6.2 Trade-off: centralized vs. partitioned matching state

A single global view of all agents and all queued conversations would, in principle, allow the theoretically best possible matches across the entire organization at every instant. In practice, this centralization becomes a scaling and availability bottleneck — every routing decision would contend on the same global state. Partitioning by skill or queue group sacrifices a small amount of theoretical optimality (an agent in queue A can never be matched to a conversation in queue B, even if they would technically be a decent fit) in exchange for the ability to scale matching horizontally and isolate failures to a single partition rather than the whole platform.

6.3 Trade-off: push assignment vs. pull / self-selection

As touched on above, an assignment-based (“push”) model gives the platform strong control over fairness and SLA compliance but removes agent autonomy over which conversations they take. A self-selection (“pull”) model preserves agent autonomy and can improve match quality when agents have good self-awareness of their own strengths, but weakens the platform’s ability to guarantee wait-time fairness and makes cherry-picking of easy conversations a real operational risk. Many mature platforms land on a middle ground: the system pushes assignments by default to guarantee baseline SLA behavior, while allowing agents limited visibility and reordering within their own already-assigned queue.

07

Performance & Scalability

This system does not have to serve a massive request rate compared to, say, an ad exchange — but every single request is on the critical path of a live human waiting for a response, and that changes what “fast enough” means.

7.1 Latency budget

From the moment a conversation is queued to the moment an agent is assigned, the target is typically well under a second under normal load — this budget breaks down roughly as: classification (10 to 100 ms, more if using an LLM-based classifier, less for a lightweight rules or ML model), queue insertion and eligibility filtering (a few milliseconds against an in-memory index), scoring candidates (a few milliseconds for a bounded candidate set), and the atomic capacity-assignment operation (single-digit milliseconds against a fast key-value store like Redis).

7.2 Scaling the matching engine

  • Partition by queue or skill group: since conversations are only ever matched against agents within their eligible skill group, the entire matching problem can be sharded by skill or queue, allowing independent router instances to handle different queues in parallel without contention.
  • In-memory agent state: agent presence and capacity are read (and often written) extremely frequently during matching — this state belongs in a fast in-memory store (Redis or similar) rather than a relational database, which would become a bottleneck under high-frequency read / write matching traffic.
  • Batch reconciliation under load spikes: during extreme volume spikes, briefly shifting from pure per-event matching to small time-windowed batch matching (for example, every 200 ms, match all queued conversations against all available agents at once using a bipartite-matching-style approach) can improve overall throughput and match quality at the cost of a small, bounded added latency.
💬
What the interviewer may ask

“How would this system handle a 10x spike in incoming chats during a major outage?” Queue depth grows and wait times increase gracefully rather than the system failing outright — admission control still accepts conversations into the queue, overflow, skill-relaxation, and bot deflection kick in automatically past configured thresholds, and router instances scale horizontally per skill-partition since the matching workload is naturally shardable.

7.3 Queueing theory lens

It is genuinely useful to think about this system through the lens of classic queueing theory (an M/M/c-style multi-server queue): conversations arrive following some arrival process, agents are the “servers,” and each conversation occupies a server for a variable “service time” (the conversation’s duration). Two practical implications follow directly from this framing:

  • Utilization near 100 percent causes wait times to explode non-linearly. Queueing theory shows that as server utilization approaches full capacity, average wait time grows much faster than linearly — this is why staffing and capacity planning targets meaningful headroom (agents rarely 100 percent utilized) rather than trying to keep every agent maximally busy at all times, even though that looks inefficient on a simple utilization dashboard.
  • Variance in conversation length matters as much as the average. A queue with highly variable conversation durations (some resolved in 1 minute, others taking 45) produces worse average wait times than a queue with the same average duration but low variance — this is why some systems separate “quick win” conversation types (password resets, simple status checks) into their own fast-turnaround queue rather than mixing them with long, complex conversations.
08

High Availability & Reliability

The routing platform is on the critical path for every incoming conversation, so its availability requirements are stricter than most SaaS services — an outage does not just delay work, it leaves live customers staring at an unresponsive screen.

  • No single point of failure in the matching path: router instances should be horizontally replicated per partition, with automatic failover if an instance holding a given skill-partition dies — another instance picks up that partition’s queue and agent set.
  • Durable queue, ephemeral matching: the queue of waiting conversations must be durably persisted (not just held in a single process’s memory) so a router crash never silently drops a customer who has been waiting — a replicated data store or a durable log (Kafka) backs the queue state.
  • Graceful degradation on classification failure: if the intent classification service is slow or unavailable, the system should fall back to a default or general skill group rather than blocking the entire conversation from being queued.
  • Presence heartbeat timeouts: an agent’s “available” status must automatically expire if their client stops heartbeating, preventing conversations from being routed to an agent who has silently disconnected.
  • Idempotent assignment handling: if an assignment message to an agent’s client is retried due to a network blip, the agent desktop must handle duplicate delivery safely rather than showing the same conversation twice.
Production example

Intercom’s routing infrastructure separates the durable conversation and queue state from the real-time matching computation specifically so that a transient issue in the matching layer does not risk losing track of a customer who is actively waiting — the queue itself remains the durable source of truth.

8.1 Failure mode analysis

FailureImpact if UnhandledMitigation
Router instance crash mid-partitionAll conversations and agents in that skill partition stop being matched.Partition ownership is leased with a short TTL; another instance detects the lapsed lease and takes over.
Presence store outageRouter cannot determine agent availability, halting all matching.Presence store is replicated with automatic failover; router falls back to a brief “hold and retry” rather than mis-routing against stale data.
Classification service outageNew conversations cannot be tagged with skill or priority.Fallback to a default or general queue with lower priority rather than blocking intake entirely.
Queue store outageNewly arriving conversations could be lost entirely.Intake writes to a durable, replicated queue store synchronously before acknowledging the customer’s request as received.

8.2 Regional failover for follow-the-sun support

Global support organizations often run agent pools that shift by time zone (a “follow-the-sun” model), and the routing system’s region-awareness needs to handle a full region going dark — whether due to an infrastructure outage or simply the end of that region’s staffed shift — without losing conversations that were mid-flight. The standard approach treats “region has no available agents” as just another input into the existing overflow chain (skill relaxation, cross-region routing) described earlier, rather than requiring a special-cased failure path — reusing the same graceful-degradation machinery for both a genuine outage and an entirely expected end-of-shift transition.

09

Security

Customer support conversations often contain PII, account details, and sensitive complaints, so the routing platform has to treat security as a first-class concern even though it is not itself a payments or authentication system.

  • Customer-agent session authentication: both the customer’s chat session and the agent’s console session are authenticated and scoped — a customer’s conversation token should only grant access to their own conversation, and an agent’s session should only expose conversations actually assigned to them.
  • PII handling in classification: intent classification (especially LLM-based) often processes potentially sensitive customer text — this data needs appropriate encryption in transit and at rest and adherence to data retention and regional data-residency policies (for example, GDPR).
  • Audit trail for assignment decisions: every routing decision (which agent, why, at what priority) should be logged immutably, both for debugging and for compliance and dispute resolution (for example, “why did this VIP customer wait 20 minutes?”).
  • Rate limiting and abuse protection: the intake endpoint needs rate limiting per customer or IP to prevent a flood of fake conversations from exhausting agent capacity or overwhelming the queue.
  • Role-based access for supervisors: manual reassignment and queue-monitoring tools must be restricted to authorized supervisor roles, since they can directly affect customer wait times and agent workload.

9.1 Threat model specific to routing systems

DoS

Queue-Flooding DoS

A malicious actor spamming fake conversations could exhaust agent capacity or dramatically inflate legitimate customers’ wait times — intake rate limiting and anomaly detection on conversation-creation patterns (for example, many conversations from the same IP or device in a short window) are necessary defenses.

AuthZ

Agent Impersonation

An attacker gaining access to an agent’s console session could view other customers’ sensitive conversation history — strict session scoping (an agent’s client only ever receives data for conversations actually assigned to them, enforced server-side, not just hidden in the UI) prevents this.

Fraud

Priority Manipulation

If priority tier is derived from client-supplied data without server-side verification against the actual customer or account record, a malicious customer could attempt to falsely claim a high-priority tier to jump the queue — priority must always be derived from authoritative server-side account data, never trusted from client input.

9.2 Data minimization in classification logging

Since intent classification logs typically capture snippets of customer message content for debugging and model improvement, it is worth deliberately minimizing what gets retained long-term — storing derived labels (topic, urgency, confidence score) durably while treating raw message text as short-retention data purged on a defined schedule, unless the customer has consented to longer retention for support-quality purposes. This reduces the blast radius of a potential data exposure and keeps the system aligned with data-minimization principles common to privacy regulations.

10

Monitoring, Logging & Metrics

A routing platform’s health cannot be inferred from CPU dashboards alone — the meaningful signals are customer-facing (wait time, abandonment) and operational (fairness, matching latency).

Metric CategoryExamples
Customer ExperienceAverage and percentile wait time, queue abandonment rate, time-to-first-response, SLA compliance rate.
Routing QualitySkill-match rate, first-contact resolution rate by routing path, transfer or reassignment rate.
Agent WorkloadConcurrent conversations per agent vs. capacity, idle time distribution, workload variance across the team (a fairness signal).
System HealthMatching decision latency, queue depth over time, router instance error or retry rate, presence heartbeat failure rate.

Wait time and abandonment rate deserve special attention as leading indicators: a rising abandonment rate (customers giving up before being connected) often signals a routing or capacity problem well before it shows up in aggregate SLA compliance numbers, since it is measuring the customers who never got far enough to be counted in a “resolved within SLA” statistic.

10.1 Dashboards for different audiences

The same underlying event stream needs to power meaningfully different dashboards for different roles: supervisors need a live queue view (current wait times, agents by status, any conversation approaching SLA breach) to intervene in real time; workforce planning teams need aggregated historical trends (volume by hour or day, staffing-to-demand ratios) to plan future shifts; and engineering needs system-health dashboards (matching latency percentiles, partition rebalance events, error rates) to keep the platform itself healthy. Designing the event schema generically enough to serve all three from one durable log, rather than building bespoke pipelines for each, keeps the system maintainable as new reporting needs inevitably emerge.

11

Deployment & Cloud

The routing layer’s statelessness (durable state lives elsewhere) makes deployment mostly conventional, but a few workload-specific choices matter.

  • Stateless router instances behind partition-aware routing: router processes themselves can be deployed as standard containerized, horizontally-scalable services, since durable state lives in the shared cache or queue layer — this makes rolling deploys and autoscaling straightforward compared to a stateful media system.
  • Multi-region deployment for global support operations: large organizations often run regional agent pools (follow-the-sun support); the intake and routing layer should be deployable per region while still supporting cross-region overflow when a region’s local agent pool is saturated.
  • Blue-green deploys are low-risk for this layer: because router instances are stateless and the queue and presence state lives externally, new versions can be rolled out with standard blue-green or canary strategies without needing to drain long-lived sessions the way a stateful media server would.
  • Autoscaling driven by queue depth, not just CPU: because the workload is bursty (support volume spikes are common and event-driven, for example, a product outage), autoscaling policies should react to queue depth and wait-time metrics, not purely CPU or memory utilization, to respond fast enough to genuine demand spikes.
12

Databases, Caching & Load Balancing

No single data store fits every workload here — the system pairs a durable, queryable store for records with a fast in-memory store for hot state, and a durable log for lifecycle events.

LayerTechnology PatternWhy
Conversation / ticket recordsRelational or document DB with read replicas.Needs durability and query flexibility for reporting, history, and compliance.
Agent presence & capacity stateIn-memory store (Redis) with atomic operations and TTL-based heartbeats.Extremely high read and write frequency during matching; must support atomic compare-and-swap-style updates.
Priority queueRedis sorted sets (score = effective priority) or a dedicated queueing system, partitioned by skill group.Needs fast insertion, fast “get highest priority eligible item,” and durability against process crashes.
Event / analytics pipelineKafka or similar durable log.Decouples real-time routing decisions from downstream analytics, SLA reporting, and ML model training pipelines.
Customer profile / CRM dataDedicated CRM service or data store, queried (and often cached) during classification and routing.Enables relationship-based routing and richer classification without embedding all customer data directly in the hot path.
Load balancing (ingress)Layer-7 load balancer with sticky WebSocket sessions per customer or agent connection.Chat connections are long-lived and stateful at the connection level, similar to any real-time messaging system.

12.1 Why presence state and conversation records are deliberately split

As with any real-time matching system, it is tempting to store everything about an agent or conversation in one place, but presence and capacity state and durable conversation records have fundamentally different consistency and durability needs. A conversation’s final transcript and resolution must be durably preserved for compliance, reporting, and dispute resolution — losing it is unacceptable. An agent’s momentary “currently handling 2 of 4 max conversations” count, by contrast, is fully reconstructable at any moment by simply counting their currently-assigned open conversations — losing it in a cache restart is an inconvenience, not a data-loss event. Recognizing which state is truly durable and which is a derived, reconstructable cache is what justifies putting the two in very differently-optimized stores.

13

APIs & Microservices

The natural service boundaries mirror the distinct responsibilities and scaling profiles in this system.

Ingress

Conversation Gateway

Connection-heavy, mostly stateless, horizontally scaled behind sticky load balancing.

Intake

Intake Service

Moderate throughput, creates durable records, relatively simple CRUD-style API.

Compute

Classification Service

Potentially the most compute-intensive component if using ML or LLM-based classification; benefits from independent scaling and possibly GPU-backed infrastructure, decoupled from the lighter-weight routing logic.

Queue

Queue Manager

High-frequency read and write against the priority queue store, partitioned by skill group for horizontal scale.

Router

Routing Engine

The matching brain; partitioned by skill group, needs low-latency access to both queue and presence state.

State

Agent Presence Service

Very high read and write frequency, in-memory backed, needs to be fast above almost all other concerns.

💬
What the interviewer may ask

“Would you combine the Queue Manager and Routing Engine into one service?” Reasonable either way. Combining them reduces network hops in the hot path (lower latency), while separating them allows each to scale and evolve independently — many production systems start combined and split them only once the queue-management and matching-logic workloads genuinely diverge in scaling needs.

14

Design Patterns & Anti-Patterns

The patterns below keep reappearing in production routing platforms; the anti-patterns are the ones that keep sinking the ones that skipped them.

14.1 Patterns to use

Pattern

Atomic CAS for Assignment

Guarantees exactly-one-agent-per-conversation correctness under concurrent matching attempts.

Pattern

Partition by Skill / Queue

Makes the matching problem embarrassingly parallel and horizontally scalable.

Pattern

Aging Priority Queues

Balance urgency-based prioritization with fairness and starvation prevention.

Pattern

Graceful Overflow Chains

Skill relaxation, cross-region overflow, and bot deflection as successive fallback layers rather than a hard queue cap.

Pattern

Event Sourcing for Lifecycle

An immutable log of every state transition (queued, assigned, transferred, closed) supports both reliability (replay and recovery) and analytics.

14.2 Anti-patterns to avoid

Do not
  • Read-then-write assignment without atomicity. Checking an agent’s availability and then separately writing the assignment invites double-assignment races under concurrent load.
  • Rely on polling-only matching. A fixed-interval batch job as the sole matching mechanism introduces unnecessary latency that customers directly experience as wait time.
  • Always route to the single “best” agent. Ignoring fairness leads to burnout for top performers and idle time for others — a real operational and morale problem, not just a technical inefficiency.
  • Treat agent presence as eventually-consistent-is-fine. Stale presence data routes conversations to unavailable agents, directly harming customer experience — this state needs to be as close to real-time as the matching decision itself.
  • Skip starvation protection in priority queues. A strict priority queue without aging can leave low-priority conversations waiting indefinitely during sustained high-priority volume.
15

Best Practices & Common Mistakes

The gap between a demo-quality routing prototype and a production one lives in the practices below — and the mistakes on the other side of the ledger.

Best PracticeCommon Mistake It Avoids
Design the matching decision as atomic from the start.Retrofitting concurrency safety after discovering double-assignment bugs in production.
Treat overflow and degradation paths as first-class requirements, not afterthoughts.A system that works perfectly under normal load but has no defined behavior during a volume spike, leading to unbounded wait times or dropped conversations.
Log every routing decision with its contributing factors.Being unable to explain to a customer or auditor why a specific routing decision was made.
Build fairness and workload-balance metrics in from day one.Discovering months later that top performers are burning out from consistently receiving disproportionate load.
Keep classification and routing loosely coupled.A slow or failing classification model (especially LLM-based) blocking the entire routing pipeline instead of degrading gracefully to a default queue.

15.1 Testing routing logic in production safely

Because routing decisions directly affect real customer wait times and real agent workloads, testing changes to the scoring function or overflow thresholds carries genuine risk — a bad weight change could silently overload part of the team or spike wait times for a customer segment. The safest pattern is shadow-mode evaluation: run a candidate routing strategy alongside the live one, logging what it would have decided without actually acting on it, and compare the two strategies’ predicted outcomes (wait time, fairness, skill-match rate) over real traffic before ever promoting the new strategy to production, and even then rolling it out to a small percentage of traffic first rather than switching over all at once.

16

Real-World Industry Examples

The design decisions above are not academic — every major helpdesk and contact center platform runs some version of them, adapted to its scale and customer base.

Zendesk

Omnichannel Routing

Tracks agent capacity as a configurable numeric value per channel (chat, messaging, email, calls can each “cost” different amounts of an agent’s total capacity) and continuously assigns from prioritized queues as capacity frees up in real time.

Intercom

Rule + Balanced Routing

Distinguishes between rule-based assignment (explicit team or skill routing rules configured by admins) and balanced or round-robin assignment within a team, giving support organizations a spectrum between fully deterministic and load-balanced routing strategies.

Salesforce

Service Cloud Omni-Channel

Supports both simple queue-based routing and a more sophisticated skills-based “Omni-Channel Flow” that can incorporate external scoring (including Einstein AI-based recommendations) into the agent-selection decision — an early production example of ML-assisted routing at scale.

LivePerson

Conversational Cloud

As one of the earliest live-chat platforms, LivePerson’s routing evolved from simple availability-based assignment toward “Conversational Cloud” AI-assisted routing that incorporates predicted conversation complexity and intent, informed by decades of accumulated chat interaction data.

AWS

Amazon Connect

Exposes routing as explicit, user-configurable “routing profiles” and priority-based queues, and integrates directly with Amazon Lex bots for pre-routing self-service deflection and with Contact Lens for AI-driven sentiment or intent analysis feeding into the routing decision — a clear production example of the classification-then-routing pipeline described in this tutorial.

17

Frequently Asked Questions

The questions below come up in nearly every real interview and design review of this system.

Q01What happens if two conversations both need the same single available specialist agent at the same instant?

The routing engine must resolve this via an atomic assignment operation (for example, an atomic capacity decrement in Redis) so exactly one conversation wins the assignment; the other conversation remains queued and is immediately re-evaluated against the next-best eligible agent or stays queued if none exists.

Q02Should routing decisions be made synchronously in the request path, or asynchronously via an event stream?

Typically a hybrid: the initial queue insertion and intake are handled synchronously (the customer needs immediate confirmation their conversation was received), while the actual matching computation is event-driven and can run asynchronously — as long as it completes fast enough that the customer does not perceive added delay, which in practice means well under a second.

Q03How do you handle an agent going offline mid-conversation?

The presence service detects the disconnect (via heartbeat timeout), and the conversation is flagged for reassignment or supervisor intervention rather than left orphaned — many systems attempt automatic reassignment to another available agent with equivalent skill, preserving full conversation context so the customer does not need to repeat themselves.

Q04Is skill-based routing always better than round robin?

Not universally — for small, generalist teams where every agent can competently handle any conversation type, the added complexity of skill-based routing may not pay for itself, and a simpler load-balanced or longest-idle strategy performs just as well with far less configuration overhead. Skill-based routing earns its complexity primarily in larger, more specialized support organizations.

Q05How would you extend this design to be truly omnichannel (chat, email, social, voice) rather than chat-only?

The core matching engine, presence and capacity model, and priority queue design generalize well — the main changes are channel-specific intake adapters (normalizing each channel’s format into the common conversation event schema) and a capacity model that weights different channel types differently, since a synchronous chat typically “costs” more of an agent’s attention than an asynchronous email does. The routing and scoring logic itself stays largely channel-agnostic.

Q06Why not just let agents pick their own next conversation from the queue instead of having the system assign it?

Self-selection (a “pull” model) is used by some organizations and has real advantages — agents can pick conversations matching their confidence level, which can improve resolution quality. But it introduces its own problems: cherry-picking (easy conversations get picked quickly, hard ones languish), inconsistent wait-time fairness across customers, and weaker SLA guarantees, since nothing forces prompt pickup of a high-priority item. Most large-scale systems use assignment (“push”) as the default specifically because it gives the platform direct control over SLA compliance and fairness, sometimes offering a limited self-selection option only within an agent’s own already-assigned queue.

18

Summary & Key Takeaways

Every design decision in this tutorial follows from one central observation: the customer is watching a spinner while this system decides.

Chat routing is fundamentally a real-time bipartite matching problem between waiting conversations and available agents, and the system’s core job is keeping both sides’ state fresh to the second so that decisions land quickly and land on the right person.

Atomicity in the assignment step is non-negotiable — exactly-one-agent-per-conversation correctness under concurrent matching attempts is the single most important guarantee in the whole design, and every other component either supports it or defers to it.

Effective routing combines hard eligibility filters (skill, language, availability) with a secondary scoring tiebreaker (load, idle time, historical fit), and deliberately introduces bounded randomization to balance optimality against fairness. Neither pure round robin nor pure “always pick the best” is sufficient on its own.

Priority queues need aging to prevent starvation of lower-priority conversations during sustained high-priority volume; strict priority without aging quietly punishes the customers who can least afford to wait.

Graceful degradation — skill relaxation, cross-region overflow, bot deflection, async fallback — must be designed as first-class behavior for the routine condition of “no agent currently available,” not treated as an edge case.

The system decomposes cleanly by responsibility and scaling profile: connection-heavy gateways, compute-heavy classification, high-frequency in-memory presence and queue state, and durable event logging for analytics and compliance — each deserving independent scaling strategies rather than being forced into a single deployment shape.

Monitoring must track customer-experience metrics (wait time, abandonment) as leading indicators, since they often reveal problems before they show up in aggregate SLA or system-health metrics.

Key takeaways an interviewer wants to hear

  • Model it as bipartite matching, not a queue with side effects. Conversations and agents are two dynamic sets, and the system exists to pair them.
  • Assignment must be atomic. An atomic capacity decrement (or single-writer partitioning) is what guarantees exactly-one-agent-per-conversation.
  • Combine hard filters with a scoring tiebreaker, and introduce bounded randomization so the top performer is not permanently on fire.
  • Age your priority queue. Otherwise low-tier customers starve during sustained high-tier volume.
  • Design overflow as a first-class chain — skill relaxation, cross-region overflow, bot deflection, async fallback — not as an edge case.
  • Partition by skill or queue to scale the matching problem horizontally and isolate failures per partition.
  • Keep presence in-memory and durable state in real stores. They have different consistency needs and belong in different systems.
  • Instrument leading indicators (wait time, abandonment, fairness variance) so regressions surface before SLA numbers move.