completefrontend
Open lab My progress
THE JAVASCRIPT FIELD GUIDE

Timers in depth

Learn what JavaScript timers guarantee, how nested and hidden-page timers are delayed, and when message tasks are a better yield.

By the end, you can
  • 01
    Read what a timer promisesTell the shortest wait and the order rules apart from a promise to run at an exact time.
  • 02
    Explain the browser's limitsWork out the 4 ms rule for timer chains, and say when Chrome checks hidden-tab timers once a second or once a minute.
  • 03
    Pick a way to run code laterExplain why schedulers may use MessageChannel, and when a timer still says what you mean.

What a timer promises

A timer asks the browser to run a function later. The function you hand over is called a callback. A timer does not pause your code. It also does not promise an exact time.

Two waits happen before the callback runs:

  • First, the browser waits for the delay you asked for.
  • Then, it waits until it is free to run the callback.

The browser runs work in pieces called tasks. A task runs from start to end without a break. The part of the browser that picks the next task is called the event loop. It picks one task at a time.

Definition

A timer delay is the shortest wait, not an exact time. After the delay, the callback is ready. Ready means the browser may run it once the current work is done and the browser's own rules allow it.

This lesson follows Events, microtasks & ordering. That lesson showed the order of plain code, events, and microtasks. This lesson looks closely at timers: the limits browsers put on them, and the few rules you can count on.

Think of an alarm clock. An alarm set for zero minutes rings as soon as the clock can handle it. It does not cut into the sentence you are saying. A phone that saves battery may also check its alarms less often when nobody is using it.

Zero means later

The smallest timer example has two lines. Line 1 sets a timer and hands over a callback. Line 2 is plain code, so it runs straight away.

A zero-delay callback still runs laterPop out in the code editor (opens in a new tab)JavaScript
setTimeout(() => console.log("second"), 0);console.log("first");

Line 1 sets a timer with a callback that prints second. Line 2 prints first right away. So the output is first, then second. A delay of 0 only means “no extra wait.” The callback still runs after the current script ends.

Left, now, in this script: line 2 prints "first". Right, later, in a timer task: the callback from line 1 prints "second". An arrow goes from now to later.now: this scriptlater: a timer taskprint "first"thenprint "second"line 2the callback from line 1
Line 1 only sets the timer. Line 2 prints first right away, and the callback prints second after the script ends.

Two timers with the same delay also follow a rule. The one you set first runs first. This rule is only about timers. It says nothing about other kinds of tasks.

Two timers with the same delay run in the order you set themPop out in the code editor (opens in a new tab)JavaScript
setTimeout(() => console.log("first timer"), 0);setTimeout(() => console.log("second timer"), 0);

Line 1 sets the first timer. Line 2 sets the second timer with the same delay. The output is first timer, then second timer. The script ends before either callback runs.

Real-life analogyAn alarm clock

An alarm set for “now” does not cut into what you are doing. It rings at the next moment the clock can handle it.

In real life: You set an alarm
In JavaScript: You set a timer with a callback
In real life: You finish your sentence
In JavaScript: The current task keeps running
In real life: The alarm rings when the clock checks
In JavaScript: The event loop picks the timer's task

Timers inside timers: the 4 ms rule

A callback can set another timer. That timer's callback can set one more, and so on. This makes a chain of timers.

The browser counts how deep the chain is. This count is the nesting level. A timer set by plain code is level 0. A timer set inside its callback is level 1, and so on.

The HTML standard is the rule book that browsers follow. It has a rule for long chains. If the nesting level is more than 5 and you ask for less than 4 milliseconds (ms), the browser waits 4 ms instead. People call this the 4 ms clamp, because it clamps (raises) the delay to 4 ms.

A small model of the 4 ms rulePop out in the code editor (opens in a new tab)JavaScript
function effectiveDelay(nestingLevel, requestedMs) {  if (nestingLevel > 5 && requestedMs < 4) return 4;  return Math.max(0, requestedMs);} console.log(effectiveDelay(5, 0));console.log(effectiveDelay(6, 0));

This code is a model. It copies the rule, but it does not use real timers. Line by line:

  • Line 1 names the function. It takes a nesting level and the delay you ask for.
  • Line 2 checks both parts of the rule: a level more than 5, and a delay less than 4. If both are true, it returns 4.
  • Line 3 returns the delay you asked for, but never less than 0.
  • Line 6 prints 0, because level 5 is not more than 5.
  • Line 7 prints 4, because level 6 is more than 5.
A chain of four timer callbacks, level 4, level 5, level 6, and level 7. Each one sets the next timer and asks for 0 ms. Levels 4 and 5 get 0 ms. Levels 6 and 7 get 4 ms.each callback calls setTimeout(next, 0)level 4level 5level 6level 70 ms0 ms4 ms4 ms5 or less: stays 0 msmore than 5: becomes 4 ms
Each callback sets the next timer and asks for 0 ms. Once the level is more than 5, the browser makes the wait 4 ms.

Why a model and not a stopwatch? A stopwatch would also measure other things, like how busy the computer is and how each browser is built. The model shows only the rule.

Step through the 4 ms rule at level 5 and level 6
Step 0 of 3Ready
Your turn: follow the blue line

Step through the model of the 4 ms rule. Each value comes from the lesson's own effectiveDelay function.

Running in
  1. script
Next: line 6
function effectiveDelay(nestingLevel, requestedMs) {  if (nestingLevel > 5 && requestedMs < 4) return 4;  return Math.max(0, requestedMs);} console.log(effectiveDelay(6, 0));
CallStoreChangeResultBlue line: runs next. Ran: already executed.
Recent returnsNothing yet. Start with the blue line.
A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.

Model a chain of timers

One timer alone is not a chain. The level goes up only when a timer's callback sets another timer. Both setTimeout and setInterval count. setInterval runs its callback again and again, and each repeat counts as one more level.

The playground below always asks for 0 ms. You change one thing: the nesting depth, which is how many timers are in the chain. For each level, it uses the same model, lists the wait, and adds the waits up. The total comes from the model. It is not how long your browser would really take.

Playground: a chain of 0 ms timers
Build a list of waits, one per levelJavaScript
function nextTimer(level, requestedMs, delays) {  const delay = level > 5 && requestedMs < 4 ? 4 : requestedMs;  delays.push(delay);  return delays;} const delays = [];nextTimer(5, 0, delays);nextTimer(6, 0, delays);console.log(delays.join(","));
Waits from the model8 ms total
level 00 ms

Asked for 0 ms. Waits 0 ms.

level 10 ms

Asked for 0 ms. Waits 0 ms.

level 20 ms

Asked for 0 ms. Waits 0 ms.

level 30 ms

Asked for 0 ms. Waits 0 ms.

level 40 ms

Asked for 0 ms. Waits 0 ms.

level 50 ms

Asked for 0 ms. Waits 0 ms.

level 64 ms

Asked for 0 ms. The rule raises it to 4 ms.

level 74 ms

Asked for 0 ms. The rule raises it to 4 ms.

Try it yourself

The model has 8 timers in the chain, each asking for 0 ms. Levels more than 5 wait 4 ms, so the total from the model is 8 ms.

This adds up the waits from the HTML rule. It is not a stopwatch, and it does not show how long your device would take.

Up to level 5, each timer that asks for 0 ms waits 0 ms in the model. From level 6 on, each one waits 4 ms. Real work can make each callback even later. So don't build an exact clock out of a chain of timers.

Timers in a hidden tab

A tab is hidden when you can't see it. For example, another tab is in front of it, or its window is minimized. The browser decides when a page counts as hidden.

A browser does not need to wake a hidden page as often as a page you are reading. Waking it less often saves battery and computer power. Running timers less often like this is called throttling.

Chrome (from version 88) puts timers into groups. One group is for hidden pages where the lightest rules don't apply. Chrome checks the timers in this group once each second. Timers that are due around the same time may then run together at that check. Chrome uses this group when the page is hidden and any of these is true:

  • The chain of timers is shorter than 5 timers.
  • The page has been hidden for less than 5 minutes.
  • The page uses WebRTC, the browser's tool for live video and voice calls.
Three browser tabs. A visible tab checks timers as asked, plus the 4 ms rule. A hidden tab checks timers once each second, and timers may run together. A tab hidden for over 5 minutes, with a long timer chain, checks timers once each minute.visiblechecks timersas askedplus the 4 ms rulehiddenchecks timerseach secondtimers may run together5+ minchecks timerseach minutetimers may run together
These are Chrome's numbers. The longer a tab stays hidden and quiet, the less often Chrome wakes its timers.

Each row of this table is one Chrome group. Read across: when the group applies, how often Chrome checks its timers, and what to remember.

How often Chrome checks timers in each group (Chrome 88)
When does it apply?How often does Chrome check?What should you remember?
The page is visible, or made sound in the last 30 seconds.As asked. This is the lightest group.The 4 ms rule still applies after enough timers in a chain.
The page is hidden, and the lightest group does not apply.About once each second.Timers due around the same time may run together.
Hidden over 5 minutes, a chain of 5 or more, silent for 30 seconds, and no WebRTC.About once each minute.Chrome calls this intensive throttling.

The lesson for your code is simple. A timer in a hidden tab can run much later than you asked. So don't make it the only record of a countdown, a payment, or a server update. Instead, save the real clock time when something starts. When the page is visible again, work out what should have happened from that saved time.

Tabs hidden for a long time

Chrome 88 added a stricter group for hidden pages with long timer chains. Chrome calls it intensive throttling. It applies only when the lighter groups don't, and all of these are true:

  • The page has been hidden for more than 5 minutes.
  • The chain of timers is 5 or more timers long.
  • The page has been silent (no sound) for at least 30 seconds.
  • The page does not use WebRTC.
Chrome's rule, confirmed

In this group, Chrome checks timers once each minute. Timers due around the same time may run together at that check. This is Chrome's own rule to save power. Other browsers do not have to do the same.

Think of the alarm clock again. A phone in battery-saver mode may check its alarms less often. After a long quiet time, it may check only about once a minute. That is good for the battery. But it means a timer that keeps asking “has anything changed?” is a poor way to notice a change.

When something changes, it is better to let the thing that changed tell you. Some examples:

  • A server can send the page an update.
  • An observer can tell you when an element changes. An observer is a browser tool that watches something and calls your function.
  • A video or audio element sends an event when it plays or stops.

Timers are still fine when your feature really means “wait at least this long.”

Which timer runs first

Three timer rules are easy to mix up:

  • A timer never runs before its delay.
  • Two timers with the same delay run in the order you set them.
  • If you set a short timer and then a longer one at the same time, the longer one does not run first.

None of these rules say a callback runs at an exact time.

The shorter timer runs firstPop out in the code editor (opens in a new tab)JavaScript
setTimeout(() => console.log("short"), 0);setTimeout(() => console.log("long"), 20);console.log("scheduled");

Line 1 sets a 0 ms timer. Line 2 sets a 20 ms timer. Line 3 prints scheduled right away. The order is always scheduled, then short, then long. This lesson makes no promise about how long each wait really is.

The HTML standard words this rule carefully. Before a timer runs, it waits for earlier timers of the same kind that asked for the same delay or less. That is why the order you set timers in matters. It does not make one fixed order for every kind of task.

When JavaScript is busy

A callback can be ready and still wait. The page's JavaScript runs on one main thread: one worker that does one thing at a time. A timer callback can't run in the middle of a task that is already running. These things can keep the main thread busy:

  • a long loop
  • working out the page layout, which is where every element goes
  • another event handler, which is a function that runs when something happens, like a click
The current script finishes before the timerPop out in the code editor (opens in a new tab)JavaScript
setTimeout(() => console.log("timer"), 0);console.log("start work");for (let score = 0; score < 3; score += 1) {  // This work runs now. The timer has to wait until it ends.}console.log("finish work");

Line 1 sets the timer. Line 2 prints start work. Lines 3 to 5 are a loop that runs right now. Line 6 prints finish work. Only after this task ends can the callback print timer. The lesson shows the order, never a measured time.

This explains many reports that a zero timeout is “slow.” Often the timer is not what made the page slow. It just had to wait behind work that was already running.

Step through a timer that waits for busy work
Step 0 of 5Ready
Your turn: follow the blue line

Step through the order of events when the script is busy. The list follows the lesson's busyTaskOrder model, not measured milliseconds.

Running in
  1. script
Next: line 1
events.push("scheduled");events.push("start work");events.push("finish work");events.push("timer");console.log(events.join(" -> "));
CallStoreChangeResultBlue line: runs next. Ran: already executed.
Recent returnsNothing yet. Start with the blue line.
A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.

setTimeout or MessageChannel

MessageChannel makes two connected ports, like the two ends of a tube. A message posted into one port comes out of the other. The other port's handler then runs in a later task, called a message task. So it runs later, like a timer. But it is not a timer, so the 4 ms rule does not apply to it.

Post a message to run code laterPop out in the code editor (opens in a new tab)JavaScript
const channel = new MessageChannel();channel.port1.onmessage = () => console.log("message task");channel.port2.postMessage("yield");console.log("scheduled");

Line 1 makes the two ports. Line 2 sets the function that runs when port1 gets a message. Line 3 posts a message into port2. Line 4 prints scheduled right away. The output is scheduled, then message task.

Some schedulers use this trick. A scheduler is code that decides when other code runs. It can post a message to pause for a moment between chunks of work, so the browser can do other things. This does not mean a message task always runs before a timer task. Don't depend on which kind of task runs first unless a rule says so.

Don't count on which task runs firstPop out in the code editor (opens in a new tab)JavaScript
const channel = new MessageChannel();channel.port1.onmessage = () => console.log("message task");setTimeout(() => console.log("timer task"), 0);channel.port2.postMessage("yield");console.log("scheduled");

Line 3 sets a timer. Line 4 posts a message. Line 5 still prints scheduled first. You can run this code and see what your browser does. But no rule fixes which of the two later tasks prints first.

Line 3, setTimeout, makes a timer task. Line 4, postMessage, makes a message task. Arrows from both go to a card that says the browser picks one. Which one runs first is not fixed.setTimeout(…, 0)postMessage(…)timer taskmessage taskthe browser picks onewhich runs first is not fixed
Line 3 makes a timer task and line 4 makes a message task. Both wait for the browser, and no rule says which one it picks first.

Use timers for the right job

Timers are useful when waiting is part of what you want. For example:

  • A save can wait until the person stops typing.
  • A message can stay on screen for a short time.
  • A failed request can wait a little before it tries again.

In each case, “later” is the goal. An exact time is not needed.

Save a draft only after the calls stopPop out in the code editor (opens in a new tab)JavaScript
function saveDraft() {  clearTimeout(saveDraft.timer);  saveDraft.timer = setTimeout(() => console.log("saved"), 300);} saveDraft();saveDraft();

This pattern is called debouncing: many quick calls turn into one. setTimeout gives back a timer ID, a number that names the timer. The function keeps that ID in saveDraft.timer.

  • Line 2 cancels the save that is still waiting, with clearTimeout.
  • Line 3 sets a new save timer for 300 ms.
  • Lines 6 and 7 call the function twice. The second call cancels the first timer, so saved prints only once.

Don't use a repeating timer just to ask “has this changed?” when something can tell you. Use these instead:

  • A WebSocket or server-sent events for server updates. Both let the server send messages to the page.
  • IntersectionObserver to learn when an element scrolls into view.
  • ResizeObserver to learn when an element changes size.
  • Media events to learn when a video or audio plays or stops.
Timer rule, browser's choice, or not promised?
  • A timer callback runs before its delay is over.
  • Two timers with the same delay run in the order you set them.
  • A long script makes a ready callback wait.
  • A hidden tab runs its timers together, less often.
  • A timer in a hidden tab runs exactly once every minute.
  • A MessageChannel message skips the 4 ms rule.
Try it yourself
0 of 6 correct

Sort each sentence into a group. Read the explanation after each choice. Timer code is easier when you know which kind of fact you rely on.

Choose a category for every card. You can change an answer at any time; Reset clears them all.

Common wrong ideas about timers

“After N milliseconds” sounds stronger than it is. It means “not before N milliseconds.” It does not mean the browser stops everything at exactly N milliseconds. Busy work and the browser's own rules can make the callback later.

  • “Zero means right now.” Zero means a later timer task, with no extra wait.
  • “A delay is a deadline.” A delay is the shortest wait. The callback can run later.
  • “A timer in a hidden tab works like a clock.” Hidden tabs check timers less often and may run them together.
  • “MessageChannel always runs first.” Don't assume an order between different kinds of tasks unless a rule gives one.

Each row of this table takes one timer phrase. Read across: what it really means, then what it does not mean.

What timer phrases mean, and what they don't mean
What people sayWhat does it really mean?What does it not mean?
setTimeout(fn, 0)Run fn in a later timer task, when the event loop can pick it.Call fn right now.
A delayThe shortest wait. The browser may run the callback later.A deadline that JavaScript keeps exactly.
The 4 ms ruleA rule for timer chains deeper than level 5.A fixed 4 ms wait for all JavaScript work.
MessageChannelA way to run code later in a message task, in a browser page.A replacement for every timer, or a task that always runs first.

These limits are not bugs in timers. They keep the page quick to respond, stop hidden pages from waking for nothing, and save battery. Your code is safe when it still works if a callback runs a bit later.

Practice exercises

Exercise 1 · Warm-upPredict the 4 ms rule

Work out the result in your head, then type the one number it prints.

Starter codePop out in the code editor (opens in a new tab)JavaScript
console.log(6 > 5 && 0 < 4 ? 4 : 0);

Answer, then press Check. Spacing and letter case don’t matter.

    Exercise 2 · Warm-upPredict what prints first

    What prints first? Type the word, not both lines.

    Starter codePop out in the code editor (opens in a new tab)JavaScript
    setTimeout(() => console.log("timer"), 0);
    console.log("script");

    Answer, then press Check. Spacing and letter case don’t matter.

      Exercise 3 · PracticeFind where the 4 ms rule starts

      A chain of timers asks for 0 ms each time. At which nesting level does the 4 ms rule first apply?

      Answer, then press Check. Spacing and letter case don’t matter.

        Exercise 4 · PracticeChoose a job for a hidden-tab timer

        Your app's tab is hidden when a timer is due. What kind of job is safe to give that timer?

        Answer, then press Check. Spacing and letter case don’t matter.

          Exercise 5 · ChallengeKeep only the latest save

          In a debounce, which function cancels the older save that is still waiting?

          Starter codePop out in the code editor (opens in a new tab)JavaScript
          let handle;
          function save() {
            clearTimeout(handle);
            handle = setTimeout(() => console.log("saved"), 0);
          }
          save();
          save();

          Answer, then press Check. Spacing and letter case don’t matter.

            Exercise 6 · ChallengeApply this to a real app

            Your order-status page asks the server for news every second. The server can also send a message when the status changes. What should you use instead of the timer?

            Answer, then press Check. Spacing and letter case don’t matter.

              Check your understanding

              For each question, ask yourself: is this a rule timers always follow, a choice the browser makes, or something nobody promised?

              Timers in depth quiz · 7 questionsScore: first tries count
              1. Question 1 of 7What does setTimeout(fn, 0) mean?

                Choose an answer to see the explanation.

              2. Question 2 of 7What does this print?

                Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
                setTimeout(() => console.log("timer"), 0);
                console.log("script");

                Choose an answer to see the explanation.

              3. Question 3 of 7In the HTML standard's rule, when does a delay below 4 ms become 4 ms?

                Choose an answer to see the explanation.

              4. Question 4 of 7In Chrome 88, what must be true for Chrome to check timers only once a minute?

                Choose an answer to see the explanation.

              5. Question 5 of 7What does a busy main thread show about a timer callback?

                Choose an answer to see the explanation.

              6. Question 6 of 7Why might a scheduler use MessageChannel?

                Choose an answer to see the explanation.

              7. Question 7 of 7A server can tell you when something changes. What is better than asking it again and again with a timer?

                Choose an answer to see the explanation.

              Key takeaways

              • A timer delay is the shortest wait, not an exact time.
              • setTimeout(fn, 0) runs fn later. It does not cut into the code that is running now.
              • Once the nesting level is more than 5, a request below 4 ms becomes 4 ms. This is the HTML standard's rule.
              • Hidden tabs check timers less often and may run them together. Chrome may check only once a minute for tabs hidden a long time.
              • Timer order rules are useful, but busy work can make every callback late.
              • MessageChannel runs code later without the 4 ms rule, but it does not always run before timers.

              Remember the one-liner.
              A timer says “not before this delay,” never “exactly at this moment.”

              Coming next: Scheduling work. There you will meet tools made for scheduling, which say what you want more clearly than a timer can.

              For everyday timer code, go back to the Timers lesson.

              CompleteFrontend Clear concepts. Working examples.