This guide provides detailed information about the Scheduling SDK's performance characteristics, optimization strategies, and best practices for high-performance scheduling operations.
The Scheduling SDK is optimized for real-world scheduling scenarios with the following performance targets:
| Busy Times | Target Response Time | Use Case |
|---|---|---|
| 1-100 | < 1ms | Individual calendars, small teams |
| 100-1,000 | < 10ms | Department scheduling, medium teams |
| 1,000-10,000 | < 100ms | Enterprise calendars, large organizations |
| 10,000+ | < 1s | Multi-tenant systems, massive datasets |
- Busy Time Processing: O(n log n) - dominated by sorting and merging operations
- Slot Generation: O(s) - where s is the number of potential slots
- Slot Filtering: O(s × n) - checking each slot against busy times
- Overall: O(n log n + s × n) in worst case
- Memory Usage: O(n + s) - storing busy times and generated slots
- Additional Overhead: Minimal - efficient in-place operations where possible
n= number of busy timess= number of generated slots
- CPU: Apple M1 Pro (2021)
- Memory: 16GB DDR4
- Node.js: v20.x
- Bun: v1.0+
// 100 busy times across 8 hours
Average: 0.8ms
95th percentile: 1.2ms
99th percentile: 2.1ms
// 1,000 busy times across 1 week
Average: 8.4ms
95th percentile: 12.1ms
99th percentile: 18.7ms
// 10,000 busy times across 1 month
Average: 92.3ms
95th percentile: 134.2ms
99th percentile: 201.5ms// 30-minute slots over 8 hours (16 slots)
Average: 0.1ms
// 15-minute slots over 8 hours (32 slots)
Average: 0.2ms
// 5-minute slots over 24 hours (288 slots)
Average: 0.8ms
// 1-minute slots over 1 week (10,080 slots)
Average: 15.2ms// Typical office day scenario
// 10 meetings, 8-hour day, 30-minute slots
const benchmark = scheduler.findAvailableSlots(workDayStart, workDayEnd, { slotDuration: 30, padding: 15 })
// Average: 1.1ms
// Enterprise calendar scenario
// 500 busy periods, 1-week range, 60-minute slots
const enterprise = scheduler.findAvailableSlots(weekStart, weekEnd, { slotDuration: 60, padding: 10 })
// Average: 45.2msEfficient Busy Time Management:
// ❌ Inefficient: Multiple separate additions
scheduler.addBusyTimes([meeting1])
scheduler.addBusyTimes([meeting2])
scheduler.addBusyTimes([meeting3])
// ✅ Efficient: Batch operations
scheduler.addBusyTimes([meeting1, meeting2, meeting3])Reuse Scheduler Instances:
// ❌ Inefficient: Creating new schedulers
function findSlots(busyTimes, start, end, options) {
const scheduler = new Scheduler(busyTimes)
return scheduler.findAvailableSlots(start, end, options)
}
// ✅ Efficient: Reuse existing scheduler
const scheduler = new Scheduler()
function findSlots(busyTimes, start, end, options) {
scheduler.clearBusyTimes()
scheduler.addBusyTimes(busyTimes)
return scheduler.findAvailableSlots(start, end, options)
}Choose Appropriate Slot Duration:
// ❌ Too granular for most use cases
{
slotDuration: 1
} // 1-minute slots
// ✅ Reasonable granularity
{
slotDuration: 15
} // 15-minute slots
{
slotDuration: 30
} // 30-minute slotsMinimize Overlapping Slots:
// ❌ Generates many overlapping slots
{ slotDuration: 60, slotSplit: 5 } // Every 5 minutes
// ✅ Reasonable overlap
{ slotDuration: 60, slotSplit: 30 } // Every 30 minutesLimit Search Ranges:
// ❌ Unnecessarily large range
const oneYear = new Date('2025-01-01')
const nextYear = new Date('2026-01-01')
// ✅ Focused range
const today = new Date()
const nextWeek = new Date(today.getTime() + 7 * 24 * 60 * 60 * 1000)Use Business Hours:
// Add non-business hours as busy times upfront
function addBusinessHoursConstraints(scheduler) {
// Block weekends, nights, holidays, etc.
scheduler.addBusyTimes(getNonBusinessHours())
}Clean Up Large Datasets:
class PerformantScheduler {
private scheduler = new Scheduler()
private maxBusyTimes = 1000
addBusyTimes(busyTimes: BusyTime[]) {
this.scheduler.addBusyTimes(busyTimes)
// Periodically clean up old busy times
if (this.scheduler.getBusyTimes().length > this.maxBusyTimes) {
this.cleanupOldBusyTimes()
}
}
private cleanupOldBusyTimes() {
const now = new Date()
const recent = this.scheduler
.getBusyTimes()
.filter(bt => bt.end.getTime() > now.getTime() - 30 * 24 * 60 * 60 * 1000) // Last 30 days
this.scheduler.clearBusyTimes()
this.scheduler.addBusyTimes(recent)
}
}function profileScheduling(name: string, fn: () => any) {
const start = performance.now()
const result = fn()
const end = performance.now()
console.log(`${name}: ${(end - start).toFixed(2)}ms`)
return result
}
// Usage
const slots = profileScheduling('Find Available Slots', () => {
return scheduler.findAvailableSlots(start, end, options)
})function monitorMemory(label: string) {
if (typeof process !== 'undefined' && process.memoryUsage) {
const usage = process.memoryUsage()
console.log(`${label} - Memory: ${Math.round(usage.heapUsed / 1024 / 1024)}MB`)
}
}
monitorMemory('Before scheduling')
const slots = scheduler.findAvailableSlots(start, end, options)
monitorMemory('After scheduling')import { performance } from 'perf_hooks'
function benchmarkScheduling() {
const scheduler = new Scheduler()
// Generate test data
const busyTimes = generateRandomBusyTimes(1000)
scheduler.addBusyTimes(busyTimes)
const iterations = 100
const times: number[] = []
for (let i = 0; i < iterations; i++) {
const start = performance.now()
scheduler.findAvailableSlots(new Date('2024-01-01T09:00:00Z'), new Date('2024-01-01T17:00:00Z'), {
slotDuration: 30,
padding: 15,
})
const end = performance.now()
times.push(end - start)
}
const avg = times.reduce((a, b) => a + b) / times.length
const p95 = times.sort()[Math.floor(times.length * 0.95)]
console.log(`Average: ${avg.toFixed(2)}ms`)
console.log(`95th percentile: ${p95.toFixed(2)}ms`)
}For very large datasets or high-throughput scenarios:
// Partition by time ranges
class PartitionedScheduler {
private schedulers = new Map<string, Scheduler>()
private getPartitionKey(date: Date): string {
return `${date.getFullYear()}-${date.getMonth()}`
}
addBusyTime(busyTime: BusyTime) {
const key = this.getPartitionKey(busyTime.start)
if (!this.schedulers.has(key)) {
this.schedulers.set(key, new Scheduler())
}
this.schedulers.get(key)!.addBusyTimes([busyTime])
}
findAvailableSlots(start: Date, end: Date, options: SchedulingOptions) {
const allSlots = []
// Find relevant partitions
const startKey = this.getPartitionKey(start)
const endKey = this.getPartitionKey(end)
for (const [key, scheduler] of this.schedulers) {
if (key >= startKey && key <= endKey) {
const slots = scheduler.findAvailableSlots(start, end, options)
allSlots.push(...slots)
}
}
return allSlots.sort((a, b) => a.start.getTime() - b.start.getTime())
}
}class CachedScheduler {
private scheduler = new Scheduler()
private cache = new Map<string, TimeSlot[]>()
findAvailableSlots(start: Date, end: Date, options: SchedulingOptions) {
const cacheKey = this.getCacheKey(start, end, options)
if (this.cache.has(cacheKey)) {
return this.cache.get(cacheKey)!
}
const slots = this.scheduler.findAvailableSlots(start, end, options)
this.cache.set(cacheKey, slots)
return slots
}
addBusyTimes(busyTimes: BusyTime[]) {
this.scheduler.addBusyTimes(busyTimes)
this.cache.clear() // Invalidate cache
}
private getCacheKey(start: Date, end: Date, options: SchedulingOptions): string {
return `${start.getTime()}-${end.getTime()}-${JSON.stringify(options)}`
}
}// ❌ Generating too many slots
{ slotDuration: 1, slotSplit: 1 } // 1-minute slots every minute
// ✅ Reasonable slot density
{ slotDuration: 15, slotSplit: 15 } // 15-minute slots every 15 minutes// ❌ Recreating scheduler repeatedly
meetings.forEach(meeting => {
const scheduler = new Scheduler([meeting])
// ... use scheduler
})
// ✅ Single scheduler instance
const scheduler = new Scheduler(meetings)// ❌ Searching entire year
const slots = scheduler.findAvailableSlots(new Date('2024-01-01'), new Date('2024-12-31'), options)
// ✅ Focused search
const slots = scheduler.findAvailableSlots(startOfWeek, endOfWeek, options)- Batch Operations: Add busy times in batches rather than individually
- Reuse Instances: Keep scheduler instances alive and reuse them
- Appropriate Granularity: Choose slot durations that match your use case
- Limit Time Ranges: Search only the time periods you need
- Clean Up: Periodically remove old busy times from long-lived schedulers
- Profile: Measure performance in your specific environment
- Cache: Consider caching for repeated identical queries
- Partition: For very large datasets, consider partitioning by time
Following these guidelines will help you achieve optimal performance for your scheduling use cases.