Building Real-Time AI Applications
Back to all articles
Engineering
18 min read11 min read

Building Real-Time AI Applications

Streaming, WebSockets, and sub-second responses. Architectures for building AI applications that feel instant and responsive.

Debasish Maji
Debasish Maji
AI Engineering Lead
May 4, 2026
Real-TimeStreamingWebSocketsLatencyUX

The Speed Imperative

Users do not wait. Every 100ms of latency costs engagement.

AI applications have a latency problem. LLM calls take seconds, not milliseconds. But users expect instant feedback. This post covers techniques for building AI applications that feel real-time.

•••

Understanding AI Latency

Latency Breakdown

ComponentTypical LatencyOptimization Potential
Network round-trip50-200msCDN, edge
Input processing10-50msOptimization
Model inference500-5000msModel choice, streaming
Output processing10-50msOptimization
Rendering10-50msClient optimization

Latency Perception

LatencyUser PerceptionAcceptable For
Under 100msInstantAll interactions
100-300msFastMost interactions
300-1000msNoticeableComplex operations
1-3sSlowExplained operations
Over 3sFrustratingBackground tasks only

Time to First Token (TTFT)

ModelTTFTFull Response
GPT-4500-1500ms3-15s
GPT-3.5200-500ms1-5s
Claude 3 Opus400-1000ms2-10s
Claude 3 Haiku150-300ms0.5-2s
Local Llama50-200ms1-10s
•••

Streaming Responses

Why Streaming Matters

ApproachPerceived LatencyActual Latency
Wait for full response5000ms5000ms
Stream tokens500ms (TTFT)5000ms
Perceived improvement90% fasterSame
Diagram
sequenceDiagram participant User participant Frontend participant Backend participant LLM User->>Frontend: Send message Frontend->>Backend: POST /chat Backend->>LLM: Stream request loop Token Stream LLM-->>Backend: Token Backend-->>Frontend: SSE: token Frontend-->>User: Render token end LLM-->>Backend: [DONE] Backend-->>Frontend: SSE: complete Frontend-->>User: Final render

Streaming Implementation

LayerTechnologyPurpose
APIServer-Sent EventsToken delivery
BackendAsync generatorsStream processing
FrontendEvent listenersToken rendering
StateIncremental updateUI state
Typescript
// Server-side streaming endpoint
export async function POST(req: Request) {
  const { messages } = await req.json();
  
  const stream = await openai.chat.completions.create({
    model: 'gpt-4',
    messages,
    stream: true,
  });

  const encoder = new TextEncoder();
  
  const readable = new ReadableStream({
    async start(controller) {
      for await (const chunk of stream) {
        const text = chunk.choices[0]?.delta?.content || '';
        if (text) {
          controller.enqueue(encoder.encode(`data: ${JSON.stringify({ text })}

`));
        }
      }
      controller.enqueue(encoder.encode('data: [DONE]

'));
      controller.close();
    },
  });

  return new Response(readable, {
    headers: {
      'Content-Type': 'text/event-stream',
      'Cache-Control': 'no-cache',
      'Connection': 'keep-alive',
    },
  });
}
Typescript
// Client-side streaming consumer
async function streamChat(message: string, onToken: (text: string) => void) {
  const response = await fetch('/api/chat', {
    method: 'POST',
    body: JSON.stringify({ messages: [{ role: 'user', content: message }] }),
  });

  const reader = response.body?.getReader();
  const decoder = new TextDecoder();

  while (true) {
    const { done, value } = await reader!.read();
    if (done) break;

    const chunk = decoder.decode(value);
    const lines = chunk.split('
').filter(line => line.startsWith('data: '));

    for (const line of lines) {
      const data = line.slice(6);
      if (data === '[DONE]') return;
      
      const { text } = JSON.parse(data);
      onToken(text);
    }
  }
}

Stream Processing Patterns

PatternUse CaseComplexity
Direct passthroughSimple chatLow
Buffer and batchSmooth renderingMedium
Transform streamContent processingMedium
Fork streamMultiple consumersHigh

Handling Stream Errors

Error TypeDetectionRecovery
Connection dropNo data timeoutReconnect, resume
Malformed dataParse errorSkip, log
Rate limit429 responseBackoff, retry
Content filterStream terminationShow partial, notify
•••

WebSocket Architecture

When to Use WebSockets

ScenarioHTTP StreamingWebSockets
Single responsePreferredOverkill
ConversationWorksBetter
CollaborativePoorPreferred
BidirectionalNot possibleRequired
Diagram
flowchart TB subgraph Clients["Clients"] C1[User 1] C2[User 2] C3[User 3] end subgraph Server["WebSocket Server"] CM[Connection Manager] MR[Message Router] SS[State Store] end subgraph AI["AI Services"] LLM[LLM API] Cache[Response Cache] end C1 <-->|WS| CM C2 <-->|WS| CM C3 <-->|WS| CM CM --> MR MR --> SS MR --> LLM LLM --> Cache style CM fill:#14b8a6,color:#fff style LLM fill:#f59e0b,color:#fff

WebSocket Design

ComponentPurposeImplementation
Connection managerHandle connectionsConnection pool
Message routerRoute messagesType-based dispatch
State syncKeep state consistentEvent sourcing
HeartbeatDetect disconnectsPing/pong
Typescript
// WebSocket server with AI streaming
import { WebSocketServer } from 'ws';

const wss = new WebSocketServer({ port: 8080 });
const connections = new Map<string, WebSocket>();

wss.on('connection', (ws, req) => {
  const userId = req.headers['x-user-id'] as string;
  connections.set(userId, ws);
  
  ws.on('message', async (data) => {
    const message = JSON.parse(data.toString());
    
    switch (message.type) {
      case 'chat':
        await handleChatMessage(ws, message);
        break;
      case 'ping':
        ws.send(JSON.stringify({ type: 'pong' }));
        break;
    }
  });
  
  ws.on('close', () => {
    connections.delete(userId);
  });
});

async function handleChatMessage(ws: WebSocket, message: any) {
  // Send typing indicator
  ws.send(JSON.stringify({ type: 'typing', status: true }));
  
  try {
    const stream = await openai.chat.completions.create({
      model: 'gpt-4',
      messages: message.messages,
      stream: true,
    });
    
    for await (const chunk of stream) {
      const text = chunk.choices[0]?.delta?.content || '';
      if (text) {
        ws.send(JSON.stringify({ type: 'token', text }));
      }
    }
    
    ws.send(JSON.stringify({ type: 'complete' }));
  } catch (error) {
    ws.send(JSON.stringify({ type: 'error', message: error.message }));
  } finally {
    ws.send(JSON.stringify({ type: 'typing', status: false }));
  }
}

Message Types

TypeDirectionPurpose
queryClient to ServerUser input
tokenServer to ClientStreaming response
completeServer to ClientResponse done
errorServer to ClientError notification
ping/pongBidirectionalKeep-alive

Scaling WebSockets

ChallengeSolutionTrade-off
Connection limitsHorizontal scalingComplexity
State sharingRedis pub/subLatency
Load balancingSticky sessionsUneven load
ReconnectionSession persistenceStorage
•••

Optimistic UI

Optimistic Patterns

PatternDescriptionRisk
Optimistic sendShow sent immediatelyFailure handling
Optimistic updateUpdate UI before confirmRollback complexity
Skeleton loadingShow structure immediatelyExpectation management
Progressive enhancementAdd details as availableState management

Optimistic Chat UI

StateDisplayActual Status
User typesInput visibleLocal
User sendsMessage in chatSending
Server receivesTyping indicatorProcessing
Tokens arriveStreaming responseReceiving
CompleteFull responseDone

Handling Failures

FailureUser ExperienceImplementation
Send failedShow retry optionLocal state + retry
Partial responseShow partial + errorKeep tokens received
TimeoutShow timeout messageCancel + option to retry
•••

Edge Computing

Edge Benefits for AI

BenefitImpactHow
Lower latency-50-150msCloser to user
Reduced TTFT-100msEdge preprocessing
Better availability99.9%+Distributed

What to Run at Edge

ComponentEdge?Reasoning
Request routingYesLow latency
Input validationYesFast rejection
CachingYesInstant hits
Small model inferenceMaybeIf latency critical
Large model inferenceNoGPU requirements

Edge Caching Strategy

Cache TypeTTLHit Rate
Static responses1 hour5-10%
Frequent queries5 min10-20%
Semantic cache1 hour20-40%
User sessionSession30-50%
•••

Parallel Processing

Parallelization Opportunities

OpportunitySpeedupImplementation
Parallel retrieval2-5xPromise.all
Speculative execution1.5-2xRace condition
Batch API calls3-10xBatching
Pre-computationInstantBackground jobs

Speculative Execution

TechniqueDescriptionRisk
Speculative retrievalFetch likely needed dataWasted compute
Speculative generationStart multiple modelsCost
Predictive prefetchAnticipate next queryAccuracy

Parallel Retrieval

ApproachLatencyCost
SequentialSum of all1x
ParallelMax of all1x
Improvement50-80%Same
•••

Perceived Performance

Psychological Tricks

TechniqueEffectImplementation
Progress indicatorsPatienceLoading states
Typing animationAnticipationAnimated dots
Skeleton screensContinuityPlaceholder UI
Chunked deliveryEngagementPartial updates

Progress Indicators

TypeBest ForImplementation
SpinnerUnknown durationAnimated icon
Progress barKnown durationPercentage
SkeletonContent loadingPlaceholder shapes
Typing dotsAI respondingAnimated dots

Content Chunking

Chunk TypeDeliveryEffect
Token by tokenReal-timeNatural
Word by wordBatchedSmoother
Sentence by sentenceLogicalCoherent
Paragraph by paragraphSemanticContextual
•••

Measuring Real-Time Performance

Key Metrics

MetricDescriptionTarget
TTFTTime to first tokenUnder 500ms
TTLBTime to last byteUnder 5s
P95 latency95th percentileUnder 2x median
Error rateFailed streamsUnder 1%

User Experience Metrics

MetricDescriptionTarget
Perceived wait timeUser-reportedUnder 1s
Abandonment rateLeft before completeUnder 5%
Retry rateUser retriedUnder 3%
SatisfactionRatingOver 4/5

Monitoring Dashboard

PanelMetricsAlert Threshold
LatencyTTFT, TTLB, P95Over 2x baseline
ThroughputRequests/secUnder 50% capacity
ErrorsError rate, typesOver 1%
StreamsActive, completedAnomaly
•••

Architecture Patterns

Simple Streaming

ComponentTechnology
ClientFetch + ReadableStream
ServerExpress + SSE
LLMOpenAI streaming API

Production Streaming

ComponentTechnology
ClientReact + SWR + streaming
EdgeVercel Edge Functions
BackendNode.js + async iterators
LLMMultiple providers + fallback
MonitoringCustom metrics + alerts

Enterprise Real-Time

ComponentTechnology
ClientReact + WebSocket + offline
EdgeGlobal edge network
BackendDistributed, stateful
LLMMulti-region, failover
ObservabilityFull tracing + RUM
•••

Key Takeaways

  1. 1Streaming is mandatory - Users cannot wait 5 seconds staring at nothing. Stream from the first token.
  1. 2TTFT is the key metric - Time to first token determines perceived speed. Optimize ruthlessly.
  1. 3Optimistic UI hides latency - Show progress immediately. Update as data arrives.
  1. 4Edge reduces latency - Every millisecond at the edge is milliseconds saved for users.
  1. 5Parallel everything - Sequential calls are slow. Parallelize retrieval, processing, and rendering.
  1. 6Perception matters - A 3-second stream feels faster than a 2-second wait. Design for psychology.

Real-time AI is not about faster models. It is about faster feedback loops. Stream early, stream often, and never leave users wondering if something is happening.

Found this helpful?

Share it with others who might benefit

TweetShare

Related articles