# Understanding Event Loop via a Scheduler

While solving a **Time-Sliced Task Scheduler** problem, I realized that my understanding of the **event loop, microtasks, and macrotasks was wrong**.

> `await` does not automatically yield control to the event loop.

* * *

### Problem Statement:

*   We need to build a scheduler that ensures no single task blocks the event loop for too long.
    
*   Instead of running tasks completely, each task should execute in small parts and pause in between.
    
*   This pause (yield) allows other tasks, including newly added or higher-priority ones, to run.
    
*   Since JavaScript cannot stop tasks forcefully, tasks must voluntarily give up control.  
    This concept is called **cooperative multitasking**, commonly used in UI frameworks to keep applications responsive.
    

* * *

### Implementation

```javascript
class TimeSlicedScheduler {
  constructor() {
    this.queue = [];
  }

  schedule(task) {
    this.queue.push(task);
  }

  async run() {
    while (this.queue.length) {
      const task = this.queue.shift();

      await task();

      // Yield control
      await new Promise(resolve => setTimeout(resolve, 0));
    }
  }
}
```

* * *

### Key Test Case

```javascript
const runPromise = scheduler.run();

await new Promise(r => setTimeout(r, 0));
events.push("event-loop");

await runPromise;
```

* * *

### What Happens WITHOUT Yield

If we remove the yield:

```javascript
await task();

//remove yield
```

**Execution Flow:**

1.  `task-1` runs
    
2.  Immediately continues
    
3.  `task-2` runs
    
4.  Then event loop runs
    

**Output:**

```plaintext
task-1
task-2
event-loop ❌
```

* * *

### Why This Happens

Because `await` uses **microtasks**, and:

> Microtasks run immediately after the current execution, before moving to the next event loop cycle.

So execution continues without any pause.

* * *

### What Changes WITH Yield

```javascript
await task();

// added yield
await new Promise(resolve => setTimeout(resolve, 0));
```

This introduces a **macrotask**, which:

*   Pauses execution
    
*   Gives control back to the event loop
    
*   Allows other pending work to run
    

* * *

### Execution Flow WITH Yield

1.  `task-1` runs
    
2.  Scheduler pauses (`setTimeout`)
    
3.  Event loop executes `"event-loop"`
    
4.  Scheduler resumes
    
5.  `task-2` runs
    

**Output:**

```plaintext
task-1
event-loop
task-2
```

* * *

### Microtask vs Macrotask (Simple View)

| Type | Examples | Behavior |
| --- | --- | --- |
| Microtask | `await`, `Promise.then` | Runs immediately |
| Macrotask | `setTimeout` | Runs in next cycle |

* * *

### Key Insight

> Microtasks don’t give control back to the event loop — macrotasks do.

* * *

### Final Takeaway

*   `await` alone is not enough for yielding
    
*   You need a macrotask (`setTimeout`) to pause execution
    
*   This pattern is used in real-world systems to avoid blocking
    

* * *

### One Line to Remember

> If you want to truly yield in JavaScript, use a macrotask — not just `await`.

This small detail completely changed how I think about async execution in JavaScript.
