Two Different Beasts: Pre-Match vs In-Play Odds
Your sportsbook needs to support both pre-match and in-play betting. They seem similar—both are odds, both update, both need to be accurate. But they're fundamentally different systems with different requirements.
This difference is why most sportsbooks run into problems around 6 months of operation: they built for pre-match (simple, predictable) and then in-play breaks everything.
Let's understand the differences.
Pre-Match Odds: The Stable Case
Pre-match odds are set 2-7 days before an event. They update, but relatively slowly and predictably.
Characteristics:
| Aspect | Value |
|---|---|
| First odds set | 5-7 days before event |
| Typical updates | Every 5-30 seconds |
| Peak velocity | 10-50 updates per minute per market |
| Market changes triggering updates | Betting volume, injury news, weather |
| User behavior | Users bookmark odds, compare across sportsbooks, plan bets |
| Typical user action | Place bet, wait (don't need immediate odds confirmation) |
| Risk tolerance | Can accept 3-5 second stale odds |
Examples:
- Monday morning: Manchester United vs Liverpool playoff on Friday set at 2.10 (United win)
- Monday afternoon: £10M in betting volume comes in on United, odds move to 2.05
- Tuesday: A key Liverpool player is injured, odds move to 2.15
- Wednesday: Closer to game, odds stabilize at 2.08-2.12 range
- Thursday: Final pre-match odds locked in
Data requirements:
- Latency: 5-10 seconds is acceptable
- Accuracy: Very important (wrong odds cost money)
- Update frequency: Every 5-30 seconds
- Historical tracking: Important (need to know odds progression)
- Market depth: Show top 5-10 markets (winner, over/under, player props)
Integration approach:
- REST API polling every 5-10 seconds is acceptable
- Can aggregate from single bookmaker without redundancy issues
- Database can be simple (no need for sub-second updates)
- Risk management systems can react on 10-30 second intervals
Cost: Low. Single provider, simple infrastructure, acceptable latency.
In-Play Odds: The Complex Case
In-play odds are updated during the event. This is where betting volume concentrates (60-70% of daily handle) and where latency matters.
Characteristics:
| Aspect | Value |
|---|---|
| First odds set | At match start (kick-off for soccer, tip-off for basketball) |
| Typical updates | Every 50-500ms |
| Peak velocity | 500-2,000 updates per minute per market |
| Market changes triggering updates | Goals, cards, fouls, time progression |
| User behavior | Watch match, react in real-time, place quick bets |
| Typical user action | See goal, place bet immediately |
| Risk tolerance | 200-300ms stale odds is noticeable; >1 second is bad |
Example timeline (Soccer match):
00:00 - Kick-off
Odds: Manchester City -0.5 (1.95) vs Liverpool (1.95)
Updates: Every 1-2 seconds as play progresses
10:23 - Manchester City scores
Odds: Manchester City -1.5 (1.85) vs Liverpool (2.10)
Updates: Happen within 500ms of goal
Volume spike: 10x normal (users placing bets on City)
15:30 - Settled, play resumes
Odds: Back to dynamic updates every 1-2 seconds
Volume: Returns to normal
40:45 - Half-time
Odds: Locked (half-time is break)
Volume: Users discuss, place bets for second half
45:00 - Second half starts
Odds: Reopen to new odds for second half
Updates: Resume at 1-2 second intervals
85:00 - Liverpool scores
Odds: Swings the other direction
Updates: 500ms
90:00 - Full-time whistle
Odds: Locked (market suspended)
Volume: Peak (everyone settling bets)
90:30 - Final settle
Odds: All markets closed, results confirmed
Data requirements:
- Latency: <300ms is necessary, <200ms is competitive
- Accuracy: Critical (wrong odds mid-match cause chaos)
- Update frequency: 50-500ms
- Historical tracking: Essential (audit why odds moved)
- Market depth: Show 20+ markets (all possible live bets)
Integration approach:
- WebSocket streaming is mandatory (REST polling is too slow)
- Multiple bookmakers for redundancy (if one feed lags, you have backup)
- Sub-second database latency required (cache layer)
- Risk management needs real-time updates (seconds matter)
Cost: High. Multiple providers, streaming infrastructure, real-time database, 24/7 monitoring.
The Integration Challenge: Supporting Both Simultaneously
Here's where most implementations struggle:
Naive approach: Build one system, use it for both.
This fails because:
- Pre-match optimisation (poll every 5 seconds) is too slow for in-play
- In-play optimisation (stream every 100ms) wastes resources on pre-match
- Risk management rules differ (can accept latency in pre-match, can't in in-play)
- Database load differs (pre-match is steady, in-play spikes 10x)
Better approach: Two-tier system with different infrastructure for each.
[Pre-Match Events]
↓
[REST API polling layer - 5-10 second updates]
↓
[Database - standard latency acceptable]
↓
[Display to users]
[In-Play Events]
↓
[WebSocket streaming layer - real-time updates]
↓
[Cache layer (Redis) - sub-second latency]
↓
[Risk management + Display to users]
Implementation Pattern 1: Event-Based Routing
Route events to the appropriate infrastructure based on state:
type EventRouter struct {
preMatchSubscribers []chan OddsUpdate
inPlaySubscribers []chan OddsUpdate
}
func (er *EventRouter) RouteUpdate(event Event, update OddsUpdate) {
switch event.State {
case "PRE_MATCH":
// Route to pre-match subscribers (polling-based)
for _, sub := range er.preMatchSubscribers {
select {
case sub <- update:
default:
// Subscriber queue full, skip (pre-match can tolerate missed updates)
}
}
case "LIVE":
// Route to in-play subscribers (streaming-based)
for _, sub := range er.inPlaySubscribers {
select {
case sub <- update:
case <-time.After(10 * time.Millisecond):
// In-play: if subscriber can't keep up, they're falling behind
metrics.Increment("inplay.subscriber_backlog")
}
}
case "CLOSED", "SUSPENDED":
// No updates to subscribers
}
}
Implementation Pattern 2: Graduated Latency
Automatically degrade data freshness based on event state:
type OddsCache struct {
preMatchTTL time.Duration // 10 seconds
inPlayTTL time.Duration // 1 second
}
func (oc *OddsCache) Get(eventID string) (OddsUpdate, error) {
event := getEvent(eventID)
odds := oc.cache.Get(eventID)
if odds == nil {
return nil, ErrNotCached
}
// Check TTL based on event state
var ttl time.Duration
switch event.State {
case "PRE_MATCH":
ttl = oc.preMatchTTL
case "LIVE":
ttl = oc.inPlayTTL
default:
return nil, ErrEventClosed
}
age := time.Since(odds.Timestamp)
if age > ttl {
// Odds are stale, probably don't serve them in-play
if event.State == "LIVE" {
return nil, ErrStaleLiveOdds
}
// Pre-match, it's okay to serve slightly stale odds
return odds, ErrOddsSlightlyStale
}
return odds, nil
}
Implementation Pattern 3: Dynamic Buffer Sizing
Pre-match can use smaller buffers (less data); in-play needs larger buffers (more volume):
type OddsBuffer struct {
preMatchBuffer chan OddsUpdate
inPlayBuffer chan OddsUpdate
}
func NewOddsBuffer() *OddsBuffer {
return &OddsBuffer{
preMatchBuffer: make(chan OddsUpdate, 100), // Small buffer, fine to drop
inPlayBuffer: make(chan OddsUpdate, 5000), // Large buffer, can't drop
}
}
Data Requirements by Market Type
Different market types have different latency needs:
Match Winner (Winner/Draw/Loss)
Pre-match: Updates every 5-30 seconds OK In-play: Updates every 500ms critical Why: Users care about who's winning now, which changes dramatically in-play
Total Goals (Over/Under 2.5)
Pre-match: Updates every 5-30 seconds OK In-play: Updates every 100ms critical Why: Goals change probability massively; market reacts immediately
Next Goal Scorer
Pre-match: Updates every 30 seconds OK (odds don't change much without play) In-play: Updates every 1-2 seconds critical (changes constantly during play) Why: Player positioning changes, substitutions happen, injury status changes
Correct Score (1-0, 2-1, etc.)
Pre-match: Updates every 10 seconds OK In-play: Updates every 500ms critical Why: Goal changes probability of this exact score; market reprices immediately
Minute of Next Goal
Pre-match: Updates every 5 seconds OK In-play: Updates every 100-200ms critical Why: Constantly resets as time progresses; probability shifts every minute
Implication: Your system needs market-specific latency targets, not one global latency SLA.
Risk Management Differences
Pre-match and in-play have different risk management needs:
Pre-Match Risk Management
Update every 10 seconds:
- Check: is my exposure > $1M on any outcome?
- Check: are my odds more than 10% out of line with market?
- Check: is there an obvious arbitrage (users can beat me)?
Action: Adjust odds manually if needed, takes 1-2 minutes
Response time requirement: 10 minutes is acceptable
In-Play Risk Management
Update every 500ms:
- Check: is my exposure > $500K on any outcome?
- Check: did odds move >5% in last 100ms (might be data error)?
- Check: is my live player exposure balanced?
Action: Automatic odds adjustment if thresholds violated
Response time requirement: 100-500ms (seconds matter)
Database Strategy Differences
Pre-Match Database
SQL Database (PostgreSQL, MySQL)
- Steady write rate: 10-20 inserts/sec
- Latency requirement: 100-500ms OK
- Historical retention: 1 year
- Cost: $500-1,000/month
Schema:
odds (event_id, market_id, timestamp, decimal_odds, bookmaker)
events (event_id, event_name, sport, start_time)
markets (market_id, market_name, type)
In-Play Database
Cache + Time-Series Database
- Redis (hot): 1,000-5,000 writes/sec, <10ms latency
- TimescaleDB (historical): 500-1,000 writes/sec, 100-500ms latency
- Cost: $5,000-10,000/month
Schema (Redis):
odds:{eventId}:{marketId} → latest odds update
events:{eventId}:live → events currently in-play
Schema (TimescaleDB):
time-series table with hypertables for fast range queries
Cost Breakdown: Pre-Match vs In-Play
Deploying both simultaneously:
Pre-Match Infrastructure:
- REST API polling: $2-5K/month
- Basic database: $1-2K/month
- Monitoring: $500-1K/month
- Subtotal: $3.5-8K/month
In-Play Infrastructure:
- WebSocket streaming: $10-20K/month (persistent connections are expensive)
- Redis cache: $3-5K/month
- Time-series database: $5-10K/month
- Real-time monitoring/alerting: $2-3K/month
- Subtotal: $20-38K/month
Total: $23.5-46K/month for both pre-match and in-play support.
This is why small sportsbooks often launch with pre-match only, then add in-play later. In-play is 5-10x more expensive.
Real-World Scaling Examples
Case Study 1: News Site (Pre-Match Focused)
Architecture:
- 50 concurrent users viewing odds
- 10 sports, 100 events per day
- Pre-match odds only
- API polling every 10 seconds
Infrastructure:
- Single REST API endpoint
- PostgreSQL database
- Redis cache
- No special infrastructure
Costs:
- API calls: 50 users × 100 events × 8,640 calls/day = 43.2M calls/day
- At $0.001/call = $43K/month
- Infrastructure: $5K/month
- Total: ~$50K/month
Scaling limits:
- Can handle 10,000 concurrent users
- At 10K users × $1 ARPU = $10K revenue/month
- Not profitable on API costs alone (need affiliate revenue)
Case Study 2: Sportsbook (In-Play Heavy)
Architecture:
- 100,000 concurrent users
- 500 events per day (50 in-play simultaneously)
- 50+ markets per event
- In-play focused (70% of volume)
- WebSocket streaming
Infrastructure:
- WebSocket servers (horizontal scaling)
- Redis (cache + session store)
- Kafka (event streaming)
- TimescaleDB (historical)
- 4 data centers (redundancy)
Costs:
- Infrastructure: $200K/month
- Data provider (in-play) handling: $100K/month
- Operations team: $150K/month
- Total: ~$450K/month
Revenue model:
- 100K users × $5 ARPU = $500K/month
- Profitable with <90% of revenue
The difference: sportsbooks can afford high infrastructure costs because volume justifies it.
Integration Testing Strategy
Before going live, you need comprehensive testing:
Load Testing
Scenario 1: Normal load
- 1,000 concurrent users
- 10 updates per second
- Should see <200ms latency
Scenario 2: Peak load
- 50,000 concurrent users
- 1,000 updates per second
- Should see <500ms latency
Scenario 3: Breaking point
- 100,000 concurrent users
- 5,000 updates per second
- Should see <2 second latency (or graceful degradation)
Scenario 4: Provider failure
- Primary provider goes offline at T=5m
- Should see <10 second failover
- Secondary provider takes over
Scenario 5: Major event
- Super Bowl kicks off
- 10x normal volume of bets
- Should maintain <500ms latency
Testing Tools
# Load testing
k6 run load-test.js
# Chaos testing (intentionally break things)
chaos kill-pod odds-service-1
chaos drop-packets odds-api
chaos delay-network 2s
# Monitoring during tests
watch 'curl http://localhost:9090/metrics | grep latency'
Hybrid Approach: Progressive Adoption
Many operators start pre-match only, then add in-play:
Month 1-2: MVP (pre-match only)
- REST API
- Basic caching
- Single provider
- Cost: $10-20K/month
Month 3-4: Feature expansion
- Add WebSocket for real-time
- Expand to 5-10 providers
- Add risk management
- Cost: $30-50K/month (3x)
Month 5-6: In-play launch
- Full in-play market coverage
- Distributed infrastructure
- 24/7 monitoring
- Cost: $100-150K/month (5x)
Month 7-12: Scale
- Optimise based on metrics
- Expand to new sports/regions
- Add player props, live markets
- Cost: Stays at $100-150K/month (efficiency gains)
Total capital spent: $300-400K over first year
ROI: If you achieve $1-2M revenue by month 12, it's highly profitable
Operational Playbook: What to Do When Things Break
When API Latency Spikes to 2+ Seconds
-
Check provider status page
- Is provider having issues? (Usually yes)
- Check their status.page.io or status endpoint
-
Check your caching
- Are you getting cache hits or missing?
- If missing, why? (Cold cache at startup, etc.)
-
Check your database
- Is database query slow?
- Is database CPU spiked?
-
Implement circuit breaker
- Stop calling slow API
- Return cached data instead
- Alert team that you're in degraded mode
-
Coordinate with provider
- Call their support line
- Ask ETA for resolution
- Prepare to switch to secondary provider
When Sportsbook Users Report Stale Odds
-
Verify with user
- What odds did they see?
- When did they see it?
- What does provider show now?
-
Calculate staleness
- If 5 seconds old: Your polling interval is too long
- If 30+ seconds old: Provider issue or network issue
-
Adjust polling interval
- If users seeing 5-10 second old odds
- Reduce polling from 10s to 5s
- Monitor cost increase
-
Consider streaming
- If continuously stale
- Need to upgrade to WebSocket
- Implement streaming layer
When Cache Hit Rate Drops Below 80%
-
Check if cold start
- Did you restart service?
- Cache is empty, needs to warm up (5-10 minutes)
-
Check if cache is working
- Are items being stored?
- Are items expiring too fast?
-
Increase cache TTL
- Pre-match: Can extend from 10s to 30s (users won't notice)
- In-play: Keep at 1-2s (must be fresh)
-
Pre-warm cache
- Load popular events at startup
- Don't wait for requests to populate cache
Moving Forward: Planning Your Integration
- Start with pre-match only if you're bootstrapping (cheaper, simpler, faster to market)
- Plan for in-play from day one (architecture decisions matter, retrofitting is expensive)
- Budget for in-play when you have the capital and user base (5-10x more expensive than pre-match)
- Choose a provider who supports both well (FairPlay does; most don't provide equal quality on both)
- Test thoroughly before launch (load testing is essential)
- Monitor religiously after launch (understand your actual performance)
- Have a playbook for common failure scenarios
The operators winning today are those who have excellent in-play odds. Pre-match is table-stakes. In-play is the competitive advantage.
FairPlay offers pre-match and in-play at the same quality level, which is rare. Most providers are good at one or the other, not both.
Related Articles:
Ready to explore BetTech for your business?
Talk to the FairPlay team about how our platform can work for your business.
Get Started








