Skip to main content

Command Palette

Search for a command to run...

How I Solved Preemptive Priority Task Scheduler in JavaScript

Updated
3 min read
How I Solved Preemptive Priority Task Scheduler in JavaScript
S

I'm Shubham (@shubhamsinghbundela), I'm a Software Engineer, a Full-stack developer, a tech enthusiast, and a technical writer here on @Hashnode. I have a strong zeal to share my acquired knowledge and I am also willing to learn from others.

When I first saw this problem, it looked like something straight out of an Operating System.

But implementing it in JavaScript?
That’s where things get interesting.


What is Preemptive Scheduling?

Preemptive scheduling is an OS technique where:

A high-priority task can interrupt a currently running low-priority task
CPU automatically switches execution

In simple words:

“Important work gets preference immediately.”


But here’s the catch in JavaScript

JavaScript does NOT support true preemption

What is “true preemption”?

  • A running task can be forcefully stopped anytime

  • CPU takes control and switches tasks

This happens in Operating Systems

In JavaScript

  • If a function starts running…

  • Nothing can stop it until it finishes

That means:

We cannot interrupt execution midway


So how do we solve this?

Since JavaScript cannot interrupt a running task, we design tasks in such a way that they pause themselves after some work and give control back to the scheduler.

This allows the system to check if any higher-priority task has arrived before continuing.

We achieve this using:

  • setTimeout

  • await (async tasks)


Problem Statement

We need to:

  • Execute tasks based on priority

  • Higher priority runs first

  • Allow new high-priority tasks to jump ahead

  • Since no true preemption → tasks must yield control


My Approach

I built a Scheduler class with:

  1. Priority Queue → to store tasks

  2. Sorting mechanism → highest priority first

  3. Execution loop → runs tasks one by one

  4. Yield mechanism → allows re-evaluation after each task


Implementation

class Scheduler {
  constructor() {
    this.queue = [];
    this.timer = null;
    this.running = false;
  }

  schedule(task, priority = 0) {
    this.queue.push({ task, priority });

    // maintain priority order
    this.queue.sort((a, b) => b.priority - a.priority);

    // start scheduler only if not already running
    if (!this.running) {
      this.running = true;
      // Don’t continue immediately
      // Pause here and come back later
      this.timer = setTimeout(() => this.run(), 0);
    }
  }

  run(onAllFinished) {
    if (this.queue.length === 0) {
      this.running = false;
      this.timer = null;
      return onAllFinished && onAllFinished(null);
    }

    const { task } = this.queue.shift();

    task((err) => {
      if (err) {
        this.running = false;
        this.timer = null;
        return onAllFinished && onAllFinished(err);
      }

      // schedule next task instead of running immediately
      this.timer = setTimeout(() => this.run(onAllFinished), 0);
    });
  }
}

const scheduler = new Scheduler();
const results = [];

const createTask = (val) => (cb) => {
  results.push(val);
  cb(null);
};

scheduler.schedule(createTask("low"), 0);
scheduler.schedule(createTask("high"), 10);
scheduler.schedule(createTask("medium"), 5);

scheduler.run((err) => {
  console.log(results);
});

What’s Actually Happening Behind the Scenes?

Let’s understand the key idea:

1. Tasks are sorted by priority

this.queue.sort((a, b) => b.priority - a.priority);

Highest priority always comes first

2. Only ONE task runs at a time

const { task } = this.queue.shift();

This keeps execution controlled

3. After every task → we pause

setTimeout(() => this.run(), 0);

This is the most important design decision

Instead of running tasks continuously:

  • We stop after each task

  • Give control back to JavaScript runtime

  • Then resume later

Why this works like preemption

Let’s say:

  1. A low-priority task runs

  2. Before next execution, a high-priority task is added

Because we paused:

  • Scheduler gets a chance to re-check the queue

  • High-priority task moves to the top

  • It runs next


Final Thought

This problem is not about setTimeout.

It’s about:

  • Understanding how JavaScript execution works

  • Designing systems within its limitations

More from this blog

Shubham Tech. Blog's

55 posts

Problem Solver | Currently Working As a Full Stack Developer, Community Leader At @Dev_Matrix | Previously Contributor at @RealDevSquad, @TeamShiksha