When your application needs real-time data updates, you have several options: polling, webhooks, or live connections. Polling - where clients repeatedly request data at intervals - is often the first solution developers reach for, but it's usually the worst choice for real-time scenarios.
Studies show that 98.5% of polling requests are wasted - they return no new data. This "Polling Madness" wastes server resources, increases costs, and still doesn't provide true real-time updates.
Imagine 10,000 users polling your API every 5 seconds. That's 10,000 requests per second your server must handle - even when there's nothing new to report. With live connections, you'd only send data when something actually changes.
Polling problems:
Live connections benefits:
| Technology | Direction | Best For |
| SignalR | Bidirectional | .NET apps needing broad compatibility, automatic fallbacks, and easy scaling |
| WebSockets | Bidirectional | Maximum performance, gaming, trading platforms, custom protocols |
| SSE | Server to Client | Simple notifications, live feeds, dashboards (no binary data) |
| Webhooks | Server to Server | Service-to-service communication, third-party integrations |
SignalR is the recommended choice for .NET applications. It automatically selects the best transport (WebSockets, SSE, or Long Polling) and provides:
// Bad - Polling for updatespublic class DashboardController : Controller{[HttpGet("api/dashboard/stats")]public async Task<IActionResult> GetStats(){// Client calls this every 5 seconds - wasteful!var stats = await _statsService.GetCurrentStats();return Ok(stats);}}// Client-side pollingsetInterval(async () => {const response = await fetch('/api/dashboard/stats');const stats = await response.json();updateDashboard(stats);}, 5000);
❌ Figure: Bad example - Polling wastes resources even when nothing has changed
// Good - SignalR Hub for real-time updatespublic class DashboardHub : Hub{public async Task SubscribeToDashboard(string dashboardId){await Groups.AddToGroupAsync(Context.ConnectionId, $"dashboard-{dashboardId}");}}// Service that pushes updates only when data changespublic class StatsService{private readonly IHubContext<DashboardHub> _hubContext;public async Task UpdateStats(string dashboardId, DashboardStats stats){// Only sends when there's actually new dataawait _hubContext.Clients.Group($"dashboard-{dashboardId}").SendAsync("StatsUpdated", stats);}}
✅ Figure: Good example - SignalR pushes updates only when data actually changes
SSE is perfect when you only need server-to-client communication and want zero dependencies:
// Good - SSE for simple server-to-client streaming[HttpGet("api/notifications/stream")]public async Task StreamNotifications(CancellationToken cancellationToken){Response.Headers.Append("Content-Type", "text/event-stream");Response.Headers.Append("Cache-Control", "no-cache");await foreach (var notification in _notificationService.GetNotificationsAsync(cancellationToken)){await Response.WriteAsync($"data: {JsonSerializer.Serialize(notification)}\n\n");await Response.Body.FlushAsync();}}
✅ Figure: Good example - SSE provides a lightweight option for unidirectional streaming
Use WebSockets directly when you need:
Live connections add complexity. Sometimes they're not worth it.
Tip: Do not over-engineer. If users only need updates every few minutes and real-time is not critical, a simple refresh button or periodic polling with proper caching might be the right choice.
Consider simpler alternatives when:
| Scenario | Recommendation |
| Chat application with instant messages | SignalR - Users expect instant delivery |
| Dashboard updated every 30 seconds | Polling - Acceptable latency, simpler implementation |
| Live sports scores | SignalR/SSE - Real-time is the product |
| Admin panel with 3 users | Polling - Over-engineering to use live connections |
| Stock trading platform | WebSockets - Milliseconds matter |
| Blog comments | Polling - Updates are infrequent |
| Collaborative document editing | SignalR - Multiple users editing simultaneously |
| Background job status | SSE - Simple server-to-client push |
Ask yourself these questions:
Live connections maintain persistent server connections, which introduces state into your web tier. When scaling horizontally:
// Program.cs - Configuring SignalR with Redis backplane for scalingbuilder.Services.AddSignalR().AddStackExchangeRedis(connectionString, options =>{options.Configuration.ChannelPrefix = RedisChannel.Literal("MyApp");});
✅ Figure: Good example - Configure a backplane when scaling SignalR horizontally
| Choose | When |
| SignalR | .NET app, need real-time, want automatic fallbacks and easy scaling |
| WebSockets | Maximum performance, binary data, custom protocols, non-.NET |
| SSE | Simple server-to-client push, no dependencies needed |
| Polling | Updates are infrequent, near real-time is OK, or building an MVP |