Docs

15.5-Timers-Intervals

9.5 Timers and Intervals

Overview

JavaScript provides built-in timing functions that allow you to schedule code execution in the future. These functions are essential for creating delays, animations, polling, debouncing, throttling, and other time-based behaviors.


Core Timer Functions

ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”
│                   TIMER FUNCTIONS                               │
ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¤
│                                                                 │
│  setTimeout(callback, delay)                                    │
│  ā”œā”€ā”€ Executes callback ONCE after delay (ms)                    │
│  └── Returns timerId for cancellation                           │
│                                                                 │
│  setInterval(callback, interval)                                │
│  ā”œā”€ā”€ Executes callback REPEATEDLY every interval (ms)           │
│  └── Returns intervalId for cancellation                        │
│                                                                 │
│  clearTimeout(timerId)                                          │
│  └── Cancels a scheduled timeout                                │
│                                                                 │
│  clearInterval(intervalId)                                      │
│  └── Stops an interval                                          │
│                                                                 │
│  requestAnimationFrame(callback)                                │
│  ā”œā”€ā”€ Schedules callback before next repaint (~60fps)            │
│  └── Optimal for visual animations                              │
│                                                                 │
ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜

setTimeout

Basic Usage

// Execute once after 1 second
setTimeout(() => {
  console.log('Hello after 1 second!');
}, 1000);

// With function reference
function greet(name) {
  console.log(`Hello, ${name}!`);
}

setTimeout(greet, 2000, 'Alice'); // Pass arguments after delay

Canceling a Timeout

const timerId = setTimeout(() => {
  console.log("This won't run");
}, 5000);

// Cancel before it executes
clearTimeout(timerId);

Zero Delay

// Zero delay doesn't mean immediate execution
// It schedules for next event loop iteration
console.log('1');

setTimeout(() => {
  console.log('3'); // Runs after sync code completes
}, 0);

console.log('2');

// Output: 1, 2, 3

setInterval

Basic Usage

// Execute every 2 seconds
let count = 0;
const intervalId = setInterval(() => {
  count++;
  console.log(`Tick ${count}`);

  // Stop after 5 ticks
  if (count >= 5) {
    clearInterval(intervalId);
  }
}, 2000);

Interval Timing Diagram

ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”
│               setInterval TIMING                                │
ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¤
│                                                                │
│  Time:   0ms      1000ms    2000ms    3000ms    4000ms         │
│          │         │         │         │         │             │
│  Start ──┤         │         │         │         │             │
│          │    ā”Œā”€ā”€ā”€ā”€ā”¤    ā”Œā”€ā”€ā”€ā”€ā”¤    ā”Œā”€ā”€ā”€ā”€ā”¤    ā”Œā”€ā”€ā”€ā”€ā”¤             │
│          │    │CB 1│    │CB 2│    │CB 3│    │CB 4│             │
│          │    ā””ā”€ā”€ā”€ā”€ā”˜    ā””ā”€ā”€ā”€ā”€ā”˜    ā””ā”€ā”€ā”€ā”€ā”˜    ā””ā”€ā”€ā”€ā”€ā”˜             │
│                                                                │
│  Note: Interval is measured from START of each callback        │
│        If callback takes longer than interval, execution       │
│        may overlap or queue                                    │
│                                                                │
ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜

Timer Accuracy

Minimum Delay

EnvironmentMinimum Delay
Active tab~4ms (browser-enforced minimum)
Background tab1000ms+ (throttled)
Node.js~1ms

Drift in Intervals

// Problem: Drift accumulates over time
let expected = Date.now();
let ticks = 0;

const intervalId = setInterval(() => {
  const now = Date.now();
  const drift = now - expected;

  console.log(`Tick ${++ticks}, drift: ${drift}ms`);
  expected += 1000; // Expected next tick

  if (ticks >= 10) clearInterval(intervalId);
}, 1000);

Self-Correcting Timer

function accurateInterval(callback, interval) {
  let expected = Date.now() + interval;

  function step() {
    const drift = Date.now() - expected;
    callback();

    expected += interval;
    setTimeout(step, Math.max(0, interval - drift));
  }

  setTimeout(step, interval);
}

Common Patterns

Pattern 1: Delay Promise

function delay(ms) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

// Usage
async function example() {
  console.log('Start');
  await delay(1000);
  console.log('After 1 second');
}

Pattern 2: Timeout Promise

function timeout(ms, message = 'Timeout') {
  return new Promise((_, reject) => {
    setTimeout(() => reject(new Error(message)), ms);
  });
}

// Race against timeout
Promise.race([fetch('/api/data'), timeout(5000, 'Request timed out')]);

Pattern 3: Debounce

ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”
│                   DEBOUNCE                                      │
ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¤
│                                                                │
│  Events:    ā—   ā—   ā—   ā—   ā—                    ā—   ā—         │
│  Time:  ────┼───┼───┼───┼───┼────────────────────┼───┼─────    │
│                                                                │
│  Wait:                  │───── 500ms ─────│                    │
│                                                                │
│  Fire:                                    āœ“                    │
│                                                                │
│  Description: Executes AFTER a pause in events                 │
│  Use case: Search input, resize handler                        │
│                                                                │
ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜
function debounce(fn, delay) {
  let timeoutId;

  return function (...args) {
    clearTimeout(timeoutId);
    timeoutId = setTimeout(() => fn.apply(this, args), delay);
  };
}

// Usage: Search input
const searchInput = document.getElementById('search');
const debouncedSearch = debounce((query) => {
  console.log('Searching:', query);
}, 300);

searchInput.addEventListener('input', (e) => {
  debouncedSearch(e.target.value);
});

Pattern 4: Throttle

ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”
│                   THROTTLE                                      │
ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¤
│                                                                │
│  Events:    ā—   ā—   ā—   ā—   ā—   ā—   ā—   ā—   ā—   ā—              │
│  Time:  ────┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───           │
│                                                                │
│  Window:    │── 500ms ──│── 500ms ──│── 500ms ──│              │
│                                                                │
│  Fire:      āœ“           āœ“           āœ“           āœ“              │
│                                                                │
│  Description: Executes AT MOST once per time window            │
│  Use case: Scroll handler, button clicks                       │
│                                                                │
ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜
function throttle(fn, limit) {
  let inThrottle = false;

  return function (...args) {
    if (!inThrottle) {
      fn.apply(this, args);
      inThrottle = true;
      setTimeout(() => (inThrottle = false), limit);
    }
  };
}

// Usage: Scroll handler
const throttledScroll = throttle(() => {
  console.log('Scroll position:', window.scrollY);
}, 100);

window.addEventListener('scroll', throttledScroll);

Debounce vs Throttle Comparison

AspectDebounceThrottle
TimingAfter quiet periodAt regular intervals
First callDelayedImmediate
Use caseSearch, resizeScroll, mousemove
Rate1 call after events stopMax N calls per second

requestAnimationFrame

Basic Usage

function animate() {
  // Update animation
  element.style.left = parseFloat(element.style.left) + 1 + 'px';

  // Continue animation
  if (parseFloat(element.style.left) < 500) {
    requestAnimationFrame(animate);
  }
}

// Start animation
requestAnimationFrame(animate);

Advantages Over setInterval

ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”
│         requestAnimationFrame vs setInterval                    │
ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¤
│                                                                │
│  setInterval(callback, 16)  (~60fps)                           │
│  ā”œā”€ā”€ āœ— Not synchronized with display refresh                   │
│  ā”œā”€ā”€ āœ— May cause jank/tearing                                  │
│  ā”œā”€ā”€ āœ— Runs in background tabs (wastes resources)              │
│  └── āœ— May fire during browser layout/paint                    │
│                                                                │
│  requestAnimationFrame(callback)                               │
│  ā”œā”€ā”€ āœ“ Synchronized with display refresh                       │
│  ā”œā”€ā”€ āœ“ Smooth animations                                       │
│  ā”œā”€ā”€ āœ“ Pauses in background tabs                               │
│  └── āœ“ Optimized by browser                                    │
│                                                                │
ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜

Animation with Delta Time

let lastTime = 0;

function animate(currentTime) {
  const deltaTime = currentTime - lastTime;
  lastTime = currentTime;

  // Move 100 pixels per second regardless of frame rate
  const speed = 100; // pixels per second
  const distance = (speed * deltaTime) / 1000;

  element.style.left = parseFloat(element.style.left) + distance + 'px';

  requestAnimationFrame(animate);
}

requestAnimationFrame(animate);

Polling Pattern

class Poller {
  constructor(asyncFn, interval) {
    this.asyncFn = asyncFn;
    this.interval = interval;
    this.running = false;
  }

  start() {
    this.running = true;
    this.poll();
  }

  stop() {
    this.running = false;
  }

  async poll() {
    if (!this.running) return;

    try {
      const result = await this.asyncFn();
      console.log('Poll result:', result);
    } catch (error) {
      console.error('Poll error:', error);
    }

    setTimeout(() => this.poll(), this.interval);
  }
}

// Usage
const poller = new Poller(
  () => fetch('/api/status').then((r) => r.json()),
  5000
);
poller.start();
// Later: poller.stop();

Timer Queue and Event Loop

ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”
│                   EVENT LOOP & TIMERS                           │
ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¤
│                                                                 │
│  ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”                                               │
│  │   Call Stack │ ◄── Synchronous code executes here            │
│  ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”¬ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜                                               │
│         │                                                       │
│         ā–¼                                                       │
│  ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”                                               │
│  │  Microtasks  │ ◄── Promise callbacks (.then, async/await)    │
│  │    Queue     │     Processed after each sync task            │
│  ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”¬ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜                                               │
│         │                                                       │
│         ā–¼                                                       │
│  ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”                                               │
│  │  Macrotasks  │ ◄── setTimeout, setInterval callbacks         │
│  │    Queue     │     Processed one at a time                   │
│  ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜                                               │
│                                                                 │
│  Order: Sync → All Microtasks → One Macrotask → Repeat          │
│                                                                 │
ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜

Example: Task Ordering

console.log('1. Sync');

setTimeout(() => console.log('4. Timeout'), 0);

Promise.resolve().then(() => console.log('3. Promise'));

console.log('2. Sync');

// Output:
// 1. Sync
// 2. Sync
// 3. Promise
// 4. Timeout

Best Practices

1. Always Clean Up Timers

class Component {
  constructor() {
    this.timers = [];
  }

  setTimeout(fn, delay) {
    const id = setTimeout(fn, delay);
    this.timers.push({ type: 'timeout', id });
    return id;
  }

  setInterval(fn, interval) {
    const id = setInterval(fn, interval);
    this.timers.push({ type: 'interval', id });
    return id;
  }

  destroy() {
    this.timers.forEach((timer) => {
      if (timer.type === 'timeout') {
        clearTimeout(timer.id);
      } else {
        clearInterval(timer.id);
      }
    });
    this.timers = [];
  }
}

2. Use AbortController for Cancellation

function cancellableDelay(ms, signal) {
  return new Promise((resolve, reject) => {
    const timeoutId = setTimeout(resolve, ms);

    signal?.addEventListener('abort', () => {
      clearTimeout(timeoutId);
      reject(new DOMException('Aborted', 'AbortError'));
    });
  });
}

// Usage
const controller = new AbortController();

cancellableDelay(5000, controller.signal)
  .then(() => console.log('Completed'))
  .catch((err) => console.log('Cancelled:', err.message));

// Cancel after 1 second
setTimeout(() => controller.abort(), 1000);

3. Avoid Nested Timers When Possible

// āŒ Messy nested timers
setTimeout(() => {
  doStep1();
  setTimeout(() => {
    doStep2();
    setTimeout(() => {
      doStep3();
    }, 1000);
  }, 1000);
}, 1000);

// āœ… Clean with async/await
async function sequence() {
  await delay(1000);
  doStep1();
  await delay(1000);
  doStep2();
  await delay(1000);
  doStep3();
}

Common Pitfalls

PitfallProblemSolution
Memory leaksTimers keep referencesClear on cleanup
this bindingWrong context in callbackUse arrow functions
String callbackssetTimeout("code", 100)Always use functions
Background throttlingIntervals slowedUse visibility API
Accumulated driftTiming inaccuracyUse self-correcting timers

Key Takeaways

  1. •setTimeout - one-time delayed execution
  2. •setInterval - repeated execution at intervals
  3. •Always clear timers - prevent memory leaks
  4. •Use requestAnimationFrame - for visual animations
  5. •Debounce - wait for pause in events
  6. •Throttle - limit execution rate
  7. •Timers are async - they go through the event loop
  8. •Timing is not guaranteed - delays are minimum, not exact
.5 Timers Intervals - JavaScript Tutorial | DeepML