A Distributed Job Scheduler is an advanced LLD problem that tests your understanding of priority queues, retry policies, distributed coordination, and the Command pattern. It's asked at Amazon, Flipkart, and companies with complex background processing needs.
Core Entities
- Job — the unit of work; has type, payload, priority, schedule
- JobDefinition — template for a job type (max retries, timeout)
- Worker — picks up jobs and executes them
- Queue — priority queue of pending jobs
- Scheduler — triggers recurring/cron jobs
- WorkerRegistry — tracks live workers via heartbeat
Command Pattern for Jobs
interface JobCommand { execute(payload: any): Promise<JobResult>; }
class SendEmailJob implements JobCommand {
async execute(payload: { to: string; subject: string; body: string }) {
await this.emailService.send(payload.to, payload.subject, payload.body);
return JobResult.success();
}
}
class GenerateReportJob implements JobCommand {
async execute(payload: { reportType: string; userId: string }) {
const report = await this.reportService.generate(payload.reportType, payload.userId);
await this.storageService.upload(report);
return JobResult.success();
}
}Priority Queue
enum JobPriority { HIGH = 1, MEDIUM = 2, LOW = 3 }
class JobQueue {
private queue: Job[] = [];
enqueue(job: Job) {
this.queue.push(job);
this.queue.sort((a, b) => {
if (a.priority !== b.priority) return a.priority - b.priority;
return a.scheduledAt.getTime() - b.scheduledAt.getTime(); // FIFO within priority
});
}
dequeue(): Job | null { return this.queue.shift() ?? null; }
}Retry with Exponential Backoff
class Worker {
async execute(job: Job) {
try {
const command = this.commandRegistry.get(job.type);
const result = await command.execute(job.payload);
job.status = JobStatus.COMPLETED;
} catch (error) {
job.attempts++;
if (job.attempts >= job.definition.maxRetries) {
job.status = JobStatus.FAILED;
this.dlq.add(job); // Dead letter queue
} else {
const delay = Math.pow(2, job.attempts) * 1000; // Exponential backoff
job.scheduledAt = new Date(Date.now() + delay);
job.status = JobStatus.PENDING;
this.queue.enqueue(job);
}
}
}
}Cron Scheduling
class CronScheduler {
schedule(jobType: string, cronExpression: string, payload: any) {
const definition = new RecurringJobDefinition(jobType, cronExpression, payload);
this.definitions.save(definition);
}
tick() { // Called every minute
const due = this.definitions.filter(d => this.isDue(d.cronExpression));
due.forEach(d => this.queue.enqueue(new Job(d)));
}
}