25-second countdown loses accuracy when the tab loses focus in Microsoft Edge

Muhammad Zayn 40 Reputation points
2026-11-11T04:13:43.9866667+00:00

I am working on a browser trivia game, Rabyss, where each round gives the player 25 seconds to type one answer. The game runs seven rounds per dive, and the countdown is central to how it plays: a correct answer typed in time earns points by rarity, and the points turn into depth.

I want the countdown to stay accurate even if the player briefly switches to another tab and comes back. I know browsers throttle timers in background tabs, and I am trying to understand how Microsoft Edge handles this. What is the recommended approach here? Should the countdown be driven by wall-clock timestamps instead of an interval, should the round pause when the tab loses focus, or is there an Edge-specific behavior I should account for? I am looking for the approach that keeps the countdown fair while the player is on the tab, without punishing a brief switch away.

The game also has multiplayer rooms and one versus one duels where both players share the same rounds, so the countdown has to stay in step for everyone in the room. I want to get the single-player countdown right first, then apply the same approach to the shared rounds.

Microsoft Edge | Microsoft Edge development
0 comments No comments

Answer accepted by question author

AI answer

2026-11-11T04:15:09.7533333+00:00

Drive the countdown from a fixed deadline and recalculate the remaining time whenever the page renders or becomes visible; use tab visibility only to implement an explicit pause policy, never as the clock itself.

Single-player implementation

setInterval() is suitable for refreshing the display, but not for measuring elapsed time. Edge can throttle and coalesce JavaScript timers in background tabs; intensive throttling can reduce them to one execution per minute after five minutes in the background.^1^ A missed callback must therefore not represent a missed second.

Store an end timestamp and derive every display value from it:

const ROUND_MS = 25_000;
let endsAt = Date.now() + ROUND_MS;
let timerId;

function renderCountdown() {
  const remainingMs = Math.max(0, endsAt - Date.now());
  countdown.textContent = Math.ceil(remainingMs / 1000);

  if (remainingMs === 0) {
    clearInterval(timerId);
    finishRound();
  }
}

timerId = setInterval(renderCountdown, 100);
document.addEventListener("visibilitychange", renderCountdown);
renderCountdown();

When the player returns after three seconds, the next render immediately shows approximately 22 seconds rather than waiting for three delayed interval callbacks. Calculating against a fixed endpoint also prevents timer drift from accumulating when individual ticks are late.^2^

Use:

  • Date.now() when the deadline must be serializable, persisted, or shared with a server.
  • performance.now() for a local-only elapsed-time measurement that should not be affected by system-clock adjustments. Microsoft documents high-resolution browser time as not being subject to system clock skew or adjustments.^3^

For this game, Date.now() is the more practical basis because the same deadline model extends to multiplayer.

Decide the background-tab rule separately

There are two valid game policies:

  1. Round continues while hidden
    Keep endsAt unchanged. On return, display the true remaining time—or finish the round if it expired. This is deterministic and maps cleanly to multiplayer.
  2. Single-player round pauses while hidden
    Record when the document becomes hidden and extend endsAt by the hidden duration when it becomes visible:
let hiddenAt = null;

document.addEventListener("visibilitychange", () => {
  if (document.hidden) {
    hiddenAt = Date.now();
  } else if (hiddenAt !== null) {
    endsAt += Date.now() - hiddenAt;
    hiddenAt = null;
    renderCountdown();
  }
});

Pausing avoids penalizing an accidental switch, but it also lets a player pause deliberately. Apply a cap, such as one short pause per round, if that matters to scoring. Do not silently infer a pause from delayed interval callbacks.

Sleeping Tabs is separate from timer throttling. Edge’s documented default sleeping timeout is 15 minutes, with configurable values from 30 seconds to 12 hours; Energy Saver can apply a shorter timeout.^4^ It normally should not determine a 25-second round’s design—the deadline calculation remains correct even if execution stops and later resumes.

Multiplayer rooms and duels

For shared rounds, make the server authoritative:

  1. The server chooses an absolute UTC deadline, such as roundEndsAt.
  2. Send that same deadline and a round ID to every room member.
  3. Each client displays max(0, roundEndsAt - estimatedServerTime).
  4. On reconnection or visibility return, request or consume the current round state and recalculate immediately.
  5. Have the server decide whether an answer was in time; do not trust the displayed client counter or the client’s clock.

SignalR is one supported option when using ASP.NET Core: it can push events to all connected clients or selected groups, including gaming scenarios.^5^ Send round state changes rather than broadcasting a decrement every second:

connection.on("RoundStarted", ({ roundId, endsAtUtcMs }) => {
  currentRoundId = roundId;
  endsAt = endsAtUtcMs - serverClockOffsetMs;
  renderCountdown();
});

connection.on("RoundEnded", ({ roundId }) => {
  if (roundId === currentRoundId) finishRound();
});

Estimate serverClockOffsetMs during connection and periodically refresh it, accounting for round-trip delay. The server deadline—not the arrival time of the “round started” message—keeps clients aligned when messages arrive at slightly different times.

A per-player hidden-tab pause should not be used in a competitive shared round: players would acquire different deadlines. If brief tab switches should be forgiven in multiplayer, define that as a server-side room rule applied consistently, rather than extending individual client timers.


References

  1. Microsoft Edge Browser Policy Documentation ...
  2. Looking for More Efficient Code - Microsoft Q&A
  3. 1 Introduction
  4. Microsoft Edge Browser Policy Documentation SleepingTabsTimeout
  5. Overview of ASP.NET Core SignalR
AI-generated content may be incorrect. Read our transparency notes for more information.

Was this answer helpful?

1 person found this answer helpful.

1 additional answer

Sort by: Newest
  1. Tom Tran (WICLOUD CORPORATION) 5,955 Reputation points Microsoft External Staff Moderator
    2026-11-11T08:00:39.54+00:00

    Hi @Muhammad Zayn ,

    Thank you for sharing the details.

    If your goal is to keep the 25-second countdown accurate without penalizing brief tab switches, I would suggest using a fixed deadline and handling pauses separately.

    For this countdown, I wouldn't recommend an Edge-specific workaround. The deadline-based approach works with background timer throttling in modern browsers, including Microsoft Edge. You can read more in the Chrome timer throttling documentation.

    For single-player rounds, you could use visibilitychange to pause when the tab becomes hidden and resume when the player returns.

    The important part is preventing the round from expiring while paused. Simply extending the deadline afterward may not work if the original timer has already ended the round.

    I would suggest trying this example:

    const ROUND_MS = 25_000;
    
    let endsAt = 0;
    let hiddenAt = null;
    let finished = false;
    let timerId = null;
    
    function remainingMs() {
      return Math.max(0, endsAt - (hiddenAt ?? Date.now()));
    }
    
    function stopTimer() {
      clearInterval(timerId);
      timerId = null;
      document.removeEventListener("visibilitychange", onVisibility);
    }
    
    function renderCountdown() {
      if (finished) return;
    
      const remaining = remainingMs();
      countdown.textContent = Math.ceil(remaining / 1000);
    
      if (remaining === 0) {
        finished = true;
        stopTimer();
        finishRound("timeout");
      }
    }
    
    function onVisibility() {
      if (document.hidden) {
        if (hiddenAt === null) {
          hiddenAt = Date.now();
        }
      } else if (hiddenAt !== null) {
        endsAt += Date.now() - hiddenAt;
        hiddenAt = null;
      }
    
      renderCountdown();
    }
    
    function startRound() {
      stopTimer();
    
      endsAt = Date.now() + ROUND_MS;
      hiddenAt = document.hidden ? Date.now() : null;
      finished = false;
    
      document.addEventListener("visibilitychange", onVisibility);
      timerId = setInterval(renderCountdown, 100);
    
      renderCountdown();
    }
    
    // Call when a correct answer ends the round early.
    function completeRound() {
      if (finished || document.hidden) return;
    
      if (remainingMs() === 0) {
        renderCountdown();
        return;
      }
    
      finished = true;
      stopTimer();
      finishRound("answered");
    }
    

    This is an illustrative single-player example that assumes answers cannot be submitted while the tab is hidden. You'll need to handle "answered" and "timeout" in your existing finishRound() function.

    For example, if a player switches tabs with 20 seconds remaining and returns 10 seconds later, the countdown resumes from 20 seconds. The finished flag prevents duplicate completion, while stopTimer() cleans up timers and event listeners.

    If you use requestAnimationFrame() for the display, keep in mind that browsers typically pause it in background tabs. I would suggest checking the deadline when an answer is submitted.

    You can also refer to the Page Visibility API documentation.

    One consideration is that players could deliberately pause to gain extra thinking time. If fairness matters, you might want to limit pauses, such as allowing one short pause per round. Alternatively, keep the deadline running while the tab is hidden if you prefer a strict 25-second limit.

    For multiplayer rooms and duels, I would recommend using one server-controlled deadline per shared round. Clients can display the remaining time using that deadline and an estimated server clock offset, while the server determines whether answers arrive in time. I would avoid individual pauses in competitive rounds.

    Disclaimer: The MDN Web Docs and Chrome Developers resources linked above are third-party documentation provided for reference.

    For your game, I would lean toward allowing brief pauses in casual single-player rounds while keeping multiplayer rounds synchronized through the server.

    I hope this helps. If you found my response helpful or informative, I would greatly appreciate it if you could follow this guidance or provide your insights.  

    Was this answer helpful?

    1 person found this answer helpful.
    0 comments No comments

Your answer

Answers can be marked as 'Accepted' by the question author and 'Recommended' by moderators, which helps users know the answer solved the author's problem.