At first glance, parallel processing sounds like a no-brainer win—using multi-core CPUs more effectively should give your code a huge speed boost. Yet in practice, this isn’t always true: some problems simply can’t be parallelized. On top of that, parallel execution introduces a whole new set of issues you’d never encounter on single-core machines.
In general, parallel code falls into two main categories of tasks. The first is the classic speed boost scenario: if your program needs to sort through 3,000 images, splitting the work means more images get processed at once. While this is definitely useful, you rarely run into tasks like this in everyday web development. (If you’re building an image-heavy site, just make sure to back those files up with reliable cloud storage.)

The more common type of parallel task deals with long-running operations that don’t demand much CPU power. A great example is waiting for a download to finish or for user input from a device: handling these tasks in parallel keeps the rest of the GUI from freezing up. Since users often get frustrated wondering “why the button won’t click,” this boosts satisfaction—even if the actual speed doesn’t improve.
If you’re hunting for new resources to code more efficiently, be sure to check out our guide to the best web design tools of 2019. And if you want to skip coding entirely, build your site with a top-tier website builder—and use our guide to pick the perfect web hosting service.
You might also want to explore our collection of CSS and JavaScript tutorials, the best JavaScript frameworks, and these essential JavaScript tools.
01. Intervalled Execution

Let’s start with a simple example using a fan favorite: the setInterval function. It takes two arguments: a reference to a function and a numeric value specified in milliseconds. After that, the function runs repeatedly every time the delay timer expires.
02. Prepping for the Sleep Function

To use the sleep() function, you’ll need to install the sleep module in a local npm project. This module acts as a bridge to your operating system’s native sleep library—don’t be surprised if your workstation’s compiler kicks in when you install the package.
03. The Pitfalls of Multitasking

When you run this program, you’ll notice right away that the worker never runs. Something’s clearly wrong with how setInterval is behaving here.
04. The Pitfalls of Multitasking, Part 2
Modern browsers give each tab its own single JavaScript thread. Take a closer look at our loop, and you’ll see it’s infinite—it runs nonstop and never hands over control. Since setInterval() relies on the message queue, its code never executes because the message handler is stuck.
05. Zeroing In on the Problem

Flavio Copes’ blog has a really interesting code snippet that demonstrates this issue. When you run it, you’ll get the output shown in the accompanying figure—all because the message queue stays blocked until the foo() function releases control of the main thread.
06. Introducing Threading

Your operating system already supports preemptive multitasking, so let’s leverage that by spawning threads with Web Workers—a dedicated API with broad browser support. The figure for this step shows the current compatibility status of the feature.
07. Creating a Separate File

Web Workers can’t be initialized with a function directly. Instead, you need a new file that contains the code you want to run in the thread. For our example, that file (named example7thread.js) has the code shown in the accompanying figure.
08. Running the Worker

Our worker is ready to go. But Node.js has a quirk: you’ll need to import the worker_threads module and pass a relative file path—something you don’t have to deal with in browsers. Also, don’t worry about missing a start() call: Web Workers automatically start running as soon as the instance is created.
09. Understanding the Results

Message delivery usually depends on communication between the runtime and the rest of your OS. Unfortunately, our infinite loop blocks this process—so only one message appears before everything freezes up.
10. Adding an Empty Loop (and Seeing What Happens)

Let’s try another experiment: add an empty loop right after creating the two worker instances. In this case, the threads will never start—because the message that triggers them never reaches the operating system.

11. Interprocess Communication
Even though workers with tight infinite loops have the issues we just saw, separate routines can still talk to each other. They do this through a message-passing interface—think of it as a tool that lets you send message objects between threads, from a sender to a receiver.
12. Why Message-Passing Matters
Beyond making thread coordination more efficient (and cutting down on “collisions” known as race conditions), message-passing to the main thread is the only way to interact with the DOM. That’s because building thread-safe GUI systems is extremely complex—so this rule avoids a lot of bugs.
13. Moving to the Browser
Implementing message-passing in Node.js is a hassle. Let’s shift the code to the web instead: start by creating an HTML file that loads examplempi.js. Just note: due to DOM origin restrictions, you can only run this code from a localhost web server (not directly from a local file).
14. Setting Up a “Mailbox”

Every worker has an onmessage property that accepts a function. This function runs automatically whenever a message comes in from the other end. In our case, the main thread will just forward incoming messages to the browser’s console.
15. Sending Messages to the Mailbox

To send a message to the mailbox, use the postMessage function. Keep in mind: workers can also have their own onmessage event, which lets them receive data from whoever “owns” the worker instance.
16. Kicking Things Off
At this point, the code is ready to run. You won’t see a flood of messages in the developer console—and that’s a good thing. The MPI interface is efficient, so data moves through the system without clogging things up.
17. Async and Await

Microsoft’s addition of the async and await keywords changed the game for C# and Visual Basic .NET—and they did the same for JavaScript. Here’s how they work: a method marked async runs cooperatively with the rest of the program, and you use await to “collect” its results when they’re ready.
18. Replacing the Old Sleep Function

Browsers have strict limits on accessing native OS functions, so we can’t reuse the sleep function we used earlier. What’s more, that function was inefficient—it froze the entire Node.js runtime. The code snippet for this step offers a better alternative.
19. Breaking Down the Code
This new code returns a Promise—another handy JavaScript feature designed to simplify multithreading. To make the code “wait,” you’ll call the Promise’s resolve method inside a setTimeout, which forces it to pause for a specified amount of time.
20. Deploying the Async Code

You can only use await inside an async function. That means we need to rewrite the worker: start by creating an async “wrapper” function, and let it handle the rest of the code’s logic.
21. Leveraging Native Power
If you’re working with Node.js and need maximum performance, don’t forget you can often build fully native modules. These modules do more than just tap into OS APIs—they can also be written in languages that compile directly to machine code, giving you a massive speed edge.
