Maximiliano Firtman's articles, notes and learning experiences for devs-firt.dev

Understanding JavaScript in the Background

What happens to JS code running in a web app when you switch tasks?

Maximiliano Firtman avatarby Maximiliano Firtman Twitter @firt About Newsletter

About 11 min reading time

Have you ever wondered what happens to the JavaScript code running in a web app when you switch tasks? Consider these scenarios:

  • You're using a React web app on your phone, and amidst network operations, you reply to a WhatsApp message with a long audio recording.
  • You're playing a game in a PWA on your PC and minimize it to take a Zoom call.
  • You're using ChatGPT on your iPad and open a new tab to check sports scores.
  • You're using X in Chrome on your Android and receive a DM right after switching to the home screen to play a game.

Does the JavaScript code in these scenarios finish executing? What about timers, animations, and pending network requests? Do these web apps continue to consume CPU resources in the background? Can they still receive messages?

It's time to delve into these questions and understand how JavaScript behaves in the background.

Background Matters #

Every web app deployed today, no matter if it's Vanilla JavaScript, or if it's using a library or framework, such as Angular, React, Svelte or Next.js, needs to understand how code execution works when interacting with modern browsers and operating system. Many times, we as developers, think that our code is executed from beginning to end without interruptions, and we don't even sit down and think what happens with our JavaScript context when our code moves to the background.

When we think about background processes in JavaScript, the concept is often shrouded in mystery. Many web developers don’t delve deeply into the nuances of background behavior, typically focusing on active user interactions instead. But understanding what happens in the background—when a user minimizes an app, switches tabs, or closes a browser—can unlock opportunities to enhance performance, optimize resource usage, and improve user experience.

Defining “Background” in Web Applications #

The term "background" has varying interpretations in web development. It could refer to a tab that is not active while the browser is still active, a browser running in the background of the operating system, or even threads executing tasks outside the main thread. There is no single, clear definition, even in the specifications provided by the W3C.

Let’s consider a working definition for the scope of this discussion: background refers to any state where our web app has been paused or stopped from executing code or interacting with the user. This could be due to minimizing the app, switching to a different window, a model important dialog from the operating system or putting the browser or the standalone web itself in the background.

Understanding this concept is crucial, as it dictates how developers optimize resources and maintain seamless user experiences across devices and platforms.

Execution Engines #

These days, JavaScript can be executed in many contexts:

  • eBooks and PDFs
  • The frontend
  • The backend with Node.js or Deno
  • As a middleware in the network layer, like when creating workers for Cloudflare.

For this article, we'll be focusing on frontend JavaScript, which means focusing on a web application, that can be executed in:

  • The browser
  • Installed web app (also known as a PWA or Progressive Web App). They have their own icon in the operating system and typically show as a standalone window. We will use the term "standalone web app" to refer to these contexts.
  • Other native apps using:
    • Hybrid approaches such as Apache Cordova or Ionic's Capacitor
    • In-app browsers from other apps (such as TikTok or Instagram)
    • A mini app framework (such as Telegram Mini Apps, WeChat Mini Programs, World mini apps or Douyin Mini Programs).

Common Background Scenarios #

To better understand JavaScript behavior in the background, let’s consider typical use cases:

  • Switching to a New Tab or Browser Window: The current tab moves to the background, but the browser is still open. This means that your web app is not visible (you may see only the tab with the favicon and title).
  • Sharing the Space with Other Active Apps: Typically, on mobile devices, this means that your web app is still partially visible on the screen but other windows, including modal dialogs from the operating system, may be rendered on top of it having the user focus.
  • Minimizing the Browser or App: The browser remains open but is no longer visible on the screen.
  • Closing a Tab, a Standalone or a Browser Window: The user closes a tab on mobile and desktop or closes a browser on desktop. On mobile devices, users don't typically have an option to close a browser, they just go to the home screen or switch to another app.
  • Switching Between Apps: The browser or standalone web app window is not anymore on the screen. On desktop devices it may be minimized, on mobile devices, switching apps often suspends the previous app.
  • OS-Level Processes: The operating system may decide to kill processes to free up memory, which could affect web apps. Users may also decide to kill apps from their operating systems.

Each of these scenarios impacts JavaScript execution in different ways, depending on browser and OS behavior. Understanding that this can happen will help to improve user experience, security and performance of your web app.

Why Background Execution Matters #

Optimizing JavaScript for background execution is crucial for several reasons:

  • Improve User Experience: Preventing outdated content or broken states improves usability. For example, resuming a game should not show progress that occurred while the app was in the background. Another example: returning to a web app that was inactive for 24 hours may not resume timers as if the user was active. It should restart, reload the authentication token, or refresh the data.
  • Resource Efficiency: Reducing unnecessary network requests, timers, and animations saves memory and battery life.
  • Avoiding Broken Transactions: Managing pending requests ensures that data integrity is maintained when users switch contexts.
  • Session Management: When a web app is paused for hours or even days, it's crucial to manage the session properly upon resumption. The time difference can impact how the app functions, requiring decisions like resuming normal activity, logging out the user to get a new authorization token, or refreshing the data. Without proper session management, the web app will be unaware of the elapsed time and may not function correctly.

Lifecycle of Background Execution #

Understanding the lifecycle of background execution involves analyzing how web apps transition through various states:

  • Active State: JavaScript runs without restrictions. Animations, timers, and event listeners work as expected.
  • Paused / Not Visible State: JavaScript runs with restrictions. Event listeners may not work, animations - **may be paused, and timers slow down.
  • Suspended / Frozen State: The JavaScript context remains in memory, but main execution is paused. For example, animations stop, and timers are not fired. The app resumes to the Active Stte if the user - **returns to the web app.
  • Destroy / Terminated: The JavaScript context and the whole web app is unloaded from memory. The app must restart if the user returns to the web app.

Mobile Device Nuances #

Mobile operating systems like Android and iOS introduce additional complexities. Apps are often suspended rather than closed, and users’ perceptions of task managers do not align with the technical realities of process management. In these cases, the OS takes control over resources to enhance battery life and optimize performance, leading to variations in background behavior across platforms.
We won't get into deep details on the lifecycle of apps on iOS and Android, but we need to remember that the browser that executes our app is a native app within those operating systems. Also, if our web app is installed as a PWA, it has standalone rights, so their lifecycle is similar to native apps.

Understanding Workers and Service Workers #

A Web Worker is a JavaScript script that runs in the background, independent of the main thread of a web application, but attached to it. This allows developers to perform computationally expensive tasks without blocking the user interface. Web Workers do not have access to the DOM but can communicate with the main thread through message passing, ensuring smooth and responsive user experiences for web applications. Sometimes developers think that if a code is running in a new thread, it's then applicable to the idea of running in the background, but actually a Web Worker typically follows the state of the main web application context. Therefore, if your web app is suspended in the background, the same will happen to their web workers.

Browsers and web runtimes today also support a different kind of thread, a Service Worker.
Service Workers operate differently from web workers. While web workers are tied to the lifecycle of a page, service workers persist independently, so they are good candidates to store code that we want to execute when the main web application is not active and not executing any code. However, browsers may pause or terminate service workers if all the clients from associated pages are inactive for an extended period. Also, browsers limit when your service worker can be woke up to execute code.
Service workers excel at handling tasks like caching, push notifications, and background syncs. Understanding their limitations and capabilities is essential for maximizing their potential.

Execution Rights while in the background on every state #

We've already stated that on different scenarios and different operating systems and browsers, execution rights in the background might differ, but let's asume that we won't have rights. What we can do to overpass that situation and execute code while in the background? Today we have several options:

1- Publish the web app in app stores

It's possible to create a PWA launcher to publish in Google Play Store, Amazon App Store, Huawei App Gallery and Apple AppStore. Because these launchers or packages are native, they may overpass the limitations of the browsers in terms of background execution.

2- Media playing

Using the right browser APIs, such as Media Session and Picture and Picture APIs, our web app will be able to run code while providing video and audio data to keep it playing in the background

3- Through Service Workers

Because Service Workers have their own lifecycle, separated from the main web app lifecycle, they may execute code, within certain restricted limitations, even if the main web app context is suspended, frozen or even if it was terminated.

Some use cases and ideas #

1- Network Requests: If your web app goes to the background while anetwork request is in process, it may be aborted.
Solutions:

  • Beacon API, if suitable
  • Web Background Synchronization API
  • Background Fetch API

2- Sync Data: Applications that sync data periodically might need to delay syncs when moved to the background. You can utilize Web Background Synchronization and Periodic Background Sync to ensure data is synchronized once the app regains focus or a network becomes available.

3- Notify the User: Use Web Notifications and Web Push APIs to keep users informed about important updates even when the app is not active. These notifications can help re-engage users and ensure they do not miss critical events.

4- Continue pending tasks: Tasks like downloading large files or processing data can be completed in the background using Service Workers or Background Fetch APIs. This ensures continuity without requiring active user interaction.

APIs for Managing Background JavaScript #

Page Visibility API #

Use the visibilitychange event to detect when a tab or a standalone web app window becomes hidden or visible:

document.addEventListener('visibilitychange', () => {
if (document.hidden) {
console.log('Tab is in the background');
// Pause or throttle tasks
} else {
console.log('Tab is in the foreground');
// Resume tasks
}
});

Monitoring visibility changes allows developers to adapt application behavior dynamically, conserving resources when necessary.

Page Lifecycle API #

The Page Lifecycle API builds upon the limitations of the Page Visibility API by providing detailed events and properties to manage the states of a web app, such as being frozen or discarded. This API helps developers understand and respond to different lifecycle states of a page, optimizing resource usage and enhancing the user experience.

Here’s a simple example using the PageLifecycle API:

// Listen for 'freeze' and 'resume' events
document.addEventListener('freeze', () => {
console.log('Page is frozen. Save state here.');
});
document.addEventListener('resume', () => {
console.log('Page has resumed. ');
});
// Check if the page was discarded
if (document.wasDiscarded) {
console.log('Page was discarded by the browser. Reloading data...');
}

This snippet demonstrates handling freeze and resume events, and checking the document.wasDiscarded property to respond to browser-induced page discards, ensuring a seamless experience for users.

Beacon API #

The Beacon API can be used to send network requests that you don't care about its response. Because of that nature, the browser can finish the request and sending the data, even if there is no more JavaScript code to receive the answer, such as when your web app is not active anymore.

document.addEventListener('visibilitychange', () => {
if (document.visibilityState === 'hidden') {
navigator.sendBeacon('/log-hidden', someData);
}
});

Web Background Synchronization #

The Background Sync API, currently supported only by Chromium-based browsers, allows you to mark pending synchronization tasks from your main web application. These tasks are identified by a custom name, such as "data-sync" or "save record." When the browser is ready, it will activate a registered service worker and execute the "sync" event handler. This handler will receive the task name as an argument, enabling you to connect to your server and complete the pending operation.

Since this process occurs within the service worker context, it operates independently of the web application, allowing synchronization to happen in the background. However, it's crucial to remember that you should implement a fallback mechanism for browsers that don't support this API.

Periodic Background Sync #

Periodic Background Sync is a feature that allows you to schedule synchronization tasks to run at regular intervals. This can be useful for ensuring that data is synchronized even if the user is not actively using your app.

A periodicsync event will be fired in the Service Worker:

  • On a synchronization time interval
  • If battery and network conditions are met
  • It's not mandatory to access the network on each execution
  • Right now, it's fired with a maximum of once every 12 hours

It works only on Chromium-based browsers.

Background Fetch #

The Background Fetch lets the webapp download files in the background managing events, such as progress, error or finished in the service worker context. While these files are downloaded, the browsers typically creates a notification so the user knows this operation is happening. These files are then typically stored locally using the FileSystem API to be used later when the user goes back to the web app.
This API is available on Chromium-based browsers and it's available as an experiment in Safari at the beginning of 2025.

Web Push Notifications #

With this API you will be able to notify the user with an OS notification from a Service Worker, that can be triggered at any time you have execution rights. The most common scenario is to create that notification when you send a push message from your server using the Web Push architecture available today on every browser.

With Apple’s recent adoption of Web Push in Safari (full support on macOS, only standalone webapps on iOS and iPadOS), developers can now engage users more effectively on all devices.
This development revitalizes interest in web push technologies, making it easier to communicate with users even when apps are not active.

Challenges in Background Execution #

Timers and Queues #

When a tab moves to the background, browsers often reduce the frequency of timer callbacks to save resources and, as we mentioned before, it may even stopped them. For instance, a setInterval callback scheduled every 10 milliseconds might execute once per second in the background. This behavior varies by browser:

  • Chrome: Reduces timer frequency significantly when a tab is hidden.
  • Firefox: Behaves similarly but may also pause timers when windows are covered on desktop.
  • Safari: Takes an aggressive approach, pausing almost all background activity to conserve battery life.

The unpredictability of timers can disrupt critical functionalities, such as data syncing, animations, or UI updates, requiring developers to implement robust fallbacks.

Animations and Frame Rates #

APIs like requestAnimationFrame stop triggering callbacks when the page is hidden. Similarly, CSS animations and transitions cease execution. This behavior ensures that background tabs do not drain unnecessary resources, but it can also create challenges for applications relying on smooth visual transitions.

Detecting time passed #

To keep the session consistent, it's always a good idea to detect when the user comes from a possible inactive or suspended state and check how much time has passed since the last time it was active. If that time distance is greater than a threshold you define, you may want to refresh, reload data, or re-authorize with your server to avoid any user experience issues.

Here’s a simple example in JavaScript to detect inactivity and handle it:

let lastActiveTime = Date.now();
const threshold = 30 * 60 * 1000; // 30 minutes in milliseconds
document.addEventListener('visibilitychange', () => {
if (!document.hidden) {
const currentTime = Date.now();
const timeElapsed = currentTime - lastActiveTime;
if (timeElapsed > threshold) {
console.log('Session expired. Refreshing data...');
// Perform your refresh or re-authorization logic here
location.reload();
} else {
console.log('Welcome back!');
}
} else {
lastActiveTime = Date.now();
}
});

Tools and Techniques for Debugging Background Behavior #

Understanding what happens in the background requires specialized tools and techniques:

Most modern browsers provide detailed insights into service workers, memory usage, and performance metrics. Chromium DevTools, such as the ones available in Google Chrome or Microsoft Edge are miles away from WebKit and Firefox devtools when talking about background execution.

Conclusion #

Understanding JavaScript's behavior in the background is essential for optimizing web apps and ensuring a seamless user experience. This article has explored how modern browsers and operating systems handle JavaScript execution when an app moves to the background. Concepts like background states, service workers, and APIs such as Page Visibility and Background Sync help developers tackle common challenges like paused timers, suspended animations, and resource constraints. By leveraging these tools, developers can design applications that adapt smoothly to interruptions.

Background execution affects user satisfaction, resource efficiency, and app reliability. Ensuring smooth transitions when resuming paused apps, managing data synchronization, and delivering timely notifications are key to maintaining functionality. With tools like browser devtools and APIs, developers can create apps that perform consistently, whether in active use or operating in the background, across various devices and platforms.

Disclaimer: This article was published at iJS Magazine for Devmio.

Half typewriter, half computer

© Maximiliano Firtman (@firt)

firt.dev contains a collection of writings, thoughts, notes and learning experiences for web and mobile app developers authored by Maximiliano Firtman.

Contact me: hi@firt.dev Personal Website Buy Me A Coffee