completefrontend
Open lab My progress
THE JAVASCRIPT FIELD GUIDE

SharedArrayBuffer & Atomics

Learn how SharedArrayBuffer and Atomics let workers share counters safely, coordinate waiting, and meet browser isolation rules.

By the end, you can
  • 01
    Share bytes between workersExplain what a SharedArrayBuffer shares, what stays private, and why you read it through a typed array such as Int32Array.
  • 02
    Pick the right Atomics toolUse load, store, add, sub, exchange, and compareExchange to read and change one shared number safely.
  • 03
    Make workers wait for each otherTell apart wait, notify, and waitAsync, and name the two headers a browser page needs before it can share memory.

One memory area, many workers

An agent is one place that runs JavaScript. The page is one agent. Each worker is another agent. Each agent has its own variables and its own call stack.

Normally, a page and a worker talk by sending messages. Each message is a copy. If the worker changes its copy, the page does not see the change.

Shared memory is different. Two or more agents read and write the very same bytes. A write by one agent shows up for the others.

Only those bytes are shared. These stay private to each agent:

  • variables
  • objects
  • functions
  • the call stack

Shared memory is a tool for a small job, such as one counter that several workers update. You plan which number goes where. You also decide which updates need a safe rule.

Definition

Atomics is a built-in object. Its operations read or change one whole number in a typed array (a fixed-size list of numbers; the next section shows one). Each operation happens as one step that can't be split. No other atomic operation can see it half done.

This lesson comes after How workers work. The next lesson is The memory model. Here you learn the small, practical tools. The next lesson explains the rules about order behind them.

Shared bytes with SharedArrayBuffer

A SharedArrayBuffer is a block of raw bytes that several agents can share. It does not hold JavaScript objects. It holds only bytes.

To read the bytes as whole numbers, put a view over them. A view is a typed array, such as Int32Array. An Int32Array reads every 4 bytes as one whole number. We call each of those numbers a cell. Index 0 is the first cell.

Start with an ordinary buffer. It is not shared, so it runs anywhere. It shows the Atomics calls before shared memory comes in.

Add five to one number cellPop out in the code editor (opens in a new tab)JavaScript
const counter = new Int32Array(new ArrayBuffer(4));Atomics.add(counter, 0, 5);console.log(Atomics.load(counter, 0));

Line by line:

  1. Line 1 makes one cell. A new cell starts at 0.
  2. Line 2 uses Atomics.add to add 5 to cell 0.
  3. Line 3 uses Atomics.load to read the cell. It prints 5.

A shared buffer changes where the bytes live. You still use views the same way. Two views over one shared buffer are not two copies. They are two names for the same bytes. A write through one name shows up through the other.

Two views of one shared cellJavaScript
const bytes = new SharedArrayBuffer(4);const pageView = new Int32Array(bytes);const workerView = new Int32Array(bytes);pageView[0] = 3;workerView[0] = 8;console.log(pageView[0], workerView[0]);
Two name tags, page view and worker view, point at one box of shared bytes. The page view writes 3, then the worker view writes 8. Both views now read 8.new SharedArrayBuffer(4)pageview1. writes 38bytesboth read 8workerview2. writes 8
pageView and workerView are two names for the same bytes. The second write replaces the first, so both names read 8.

Line by line:

  1. Line 1 makes 4 shared bytes.
  2. Lines 2 and 3 make two views over those same bytes. The names say who would use each one: the page and a worker.
  3. Line 4 writes 3 through pageView.
  4. Line 5 writes 8 through workerView. This replaces the 3.
  5. Line 6 prints 8 8. Both views read the same cell.

In a real app, the page and the worker each make their own view, the same way. Run this code in Node.js or in a page with cross-origin isolation (a browser safety setting you will meet below). The lesson editor has neither, so SharedArrayBuffer does not exist there.

The table compares three ways data can reach another agent, plus a normal variable. Read each row from left to right.

Copied, handed over, or shared
What you useWhat the other agent getsWhat happens after a later write
A normal messageA copy of the values.Each side changes its own copy. The other side doesn't see it.
A transfer (handing over)The thing itself, such as an ArrayBuffer.The sender can no longer use it.
A SharedArrayBufferThe same block of bytes.Every view sees the write.
A normal variableNothing. It never leaves its agent.Only its own agent sees the write.

Browsers only allow shared memory on isolated pages. So check first, before you make a shared buffer.

Make shared bytes only when the page allows itPop out in the code editor (opens in a new tab)JavaScript
if (typeof SharedArrayBuffer === "function" && crossOriginIsolated) {  const counter = new Int32Array(new SharedArrayBuffer(4));  Atomics.add(counter, 0, 5);  console.log(Atomics.load(counter, 0));} else {  console.log("Shared memory needs cross-origin isolation");}

Line by line:

  1. Line 1 checks two things. Does SharedArrayBuffer exist? Is crossOriginIsolated true?
  2. Lines 2 and 3 make shared bytes, a view, and add 5.
  3. Line 4 prints 5 on an isolated page.
  4. On any other page, line 6 prints a short message instead of throwing an error.
Real-life analogyA shared whiteboard

Picture a whiteboard in a room. Everyone can read the number on it. If two people try to change it at the same time, they need a rule. One person writes at a time.

In real life: One whiteboard is in the room
In JavaScript: One SharedArrayBuffer holds bytes
In real life: Everyone sees the same number
In JavaScript: Every view reads the same cell
In real life: One marker changes the number
In JavaScript: An atomic operation changes a cell
In real life: Each person has a private notebook
In JavaScript: Each agent keeps private variables

How two workers can lose an update

count = count + 1 looks like one step. It is really three steps:

  1. read the count
  2. add one
  3. write the new count back

Two workers can both read 0 before either one writes. Both work out 1. Both write 1. The second write replaces the first. The team wanted two increases, but the count is 1.

This is called a lost update. It is why a shared counter needs more than shared bytes. In real code, the timing changes from run to run, so the bug may not show up when you test. This lesson uses a model instead. The model always picks the same order, so you can see the problem every time.

A model of a lost updatePop out in the code editor (opens in a new tab)JavaScript
let count = 0;const firstRead = count;const secondRead = count;count = firstRead + 1;count = secondRead + 1;console.log(count);
The first worker read 0 and writes 1. The second worker also read 0 and also writes 1. The count box ends at 1, not 2.count = read + 1first read 0writes 11countsecond read 0writes 1count is 1, not 2
Both workers read 0 before either one writes. Each writes 0 + 1, so the count ends at 1 instead of 2.

Line by line:

  1. Line 1 starts the count at 0.
  2. Lines 2 and 3 are the two reads. Both workers save 0.
  3. Lines 4 and 5 are the two writes. Both write 0 + 1, which is 1.
  4. Line 6 prints 1.

The model picks the bad order on purpose: both reads first. Real code does not lose an update every time. It only can.

Step through a lost update
Step 0 of 6Ready
Your turn: follow the blue line

Step through a model of a lost update. Real code does not lose an update every time; it only can.

Running in
  1. script
Next: line 1
const firstRead = count;const secondRead = count;count = firstRead + 1;count = secondRead + 1;console.log(count);
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.

Step through the model. Stop at each read and each write. This replays the lesson's own code; it is not a real debugger. Watch the second worker: it still holds the old 0 when it writes.

Playground: pick the order of reads and writes
Read, add, write without AtomicsJavaScript
let count = 0;const firstRead = count;const secondRead = count;count = firstRead + 1;count = secondRead + 1;console.log(count);
What the model doescount ends at 1
  1. first reads 0
  2. second reads 0
  3. first writes 1
  4. second writes 1
The safe version, with AtomicsJavaScript
const count = new Int32Array(new ArrayBuffer(4));
Atomics.add(count, 0, 1);
Atomics.add(count, 0, 1);
console.log(Atomics.load(count, 0));
Try it yourself
Choose the order

The count ends at 1. Both workers read 0 before either one wrote. So both wrote 1, and the second write replaced the first.

This model always uses the order you pick. Real code without Atomics can run in any order, so it does not lose an update every time; it only can.

Try another order. If one worker finishes before the other reads, the count ends at 2. If both read first, both use the old 0, and the count ends at 1. Only the order changes. Nothing else does.

Safe one-step updates with Atomics

Use an atomic operation when several agents change the same cell. It runs as one step. No other atomic operation can slip in between its read and its write.

This keeps one cell safe. It does not make a whole app safe. You still decide what each cell means and in what order agents use them.

Read one stored scorePop out in the code editor (opens in a new tab)JavaScript
const score = new Int32Array(new ArrayBuffer(4));Atomics.store(score, 0, 3);console.log(Atomics.load(score, 0));

Line 1 makes one cell. Line 2 stores 3 in it. Line 3 reads it with Atomics.load and prints 3. Use load when you want to know the current value.

Store a new scorePop out in the code editor (opens in a new tab)JavaScript
const score = new Int32Array(new ArrayBuffer(4));console.log(Atomics.store(score, 0, 3));console.log(Atomics.load(score, 0));

Line 2 writes 3 with Atomics.store. The call gives back the value it stored, so it prints 3. Line 3 reads the cell again and also prints 3. A store only changes the cell. It does not wake up a worker that is waiting.

Add points and read the new totalPop out in the code editor (opens in a new tab)JavaScript
const score = new Int32Array(new ArrayBuffer(4));Atomics.store(score, 0, 3);console.log(Atomics.add(score, 0, 2));console.log(Atomics.load(score, 0));

Line 2 sets the cell to 3. Line 3 adds 2. But Atomics.add gives back the old value, so it prints 3. Line 4 reads the cell after the change and prints 5. Getting the old value back is useful for counters, and for compare and swap later on.

Read, write, and change one scorePop out in the code editor (opens in a new tab)JavaScript
const score = new Int32Array(new ArrayBuffer(4));Atomics.store(score, 0, 3);console.log(Atomics.load(score, 0));console.log(Atomics.add(score, 0, 2));console.log(Atomics.sub(score, 0, 1));console.log(Atomics.exchange(score, 0, 9));console.log(Atomics.compareExchange(score, 0, 9, 12));console.log(Atomics.load(score, 0));

Line by line:

  1. Line 1 makes one cell.
  2. Line 2 stores 3.
  3. Line 3 reads it and prints 3.
  4. Line 4 adds 2. It prints the old value, 3. The cell is now 5.
  5. Line 5 subtracts 1. It prints the old value, 5. The cell is now 4.
  6. Line 6 swaps in 9 with exchange. It prints the old value, 4.
  7. Line 7 expects 9, finds 9, and puts in 12. It prints the old value, 9.
  8. Line 8 reads the cell and prints 12.

The table lists each tool. Read a row as: the tool, what it does to the cell, and what the call gives back.

Small atomic tools for one number cell
ToolWhat it does to the cellWhat it gives back
loadReads the cell.The value in the cell right now.
storeWrites a new value into the cell.The value it wrote.
add and subAdds to or subtracts from the cell.The old value, from before the change.
exchangePuts in a new value, no matter what is there.The old value.
compareExchangePuts in a new value only if the cell holds the value you expect.The old value, whether or not it changed the cell.

These tools work on whole-number typed arrays only, not on objects. For rich data or rare updates, send messages instead. A shared cell is useful because its job is small. That makes it easy to explain, check, and test.

Compare and swap: change it only if it still matches

Compare and swap means: "Change the cell only if it still holds the value I saw." In JavaScript, this is Atomics.compareExchange. It takes four inputs:

  • the typed array
  • the cell index
  • the value you expect to find
  • the new value to put in

It checks and replaces in one atomic step. It always gives back the old value, whether the check passed or not.

Replace a score only if it matchesPop out in the code editor (opens in a new tab)JavaScript
const score = new Int32Array(new ArrayBuffer(4));Atomics.store(score, 0, 3);console.log(Atomics.compareExchange(score, 0, 3, 8));console.log(Atomics.load(score, 0));
The score box holds 3. compareExchange expects 3, sees 3, and puts 8 in the box. It returns the old value, 3.compareExchange(score, 0, 3, 8)3scorebefore3 matches 38scoreafter3returns
The cell still holds the expected 3, so 8 goes in. The call gives back the old value, 3.

Line 2 stores 3. Line 3 expects 3 and finds 3. So it puts in 8 and prints the old value, 3. Line 4 reads the cell and prints 8. The check and the write are one step. They are not an if check followed later by a separate write.

Step through compare and swap
Step 0 of 4Ready
Your turn: follow the blue line

Step through a real compare and swap. The old value it gives back tells you whether the cell held the value you expected.

Running in
  1. script
Next: line 1
Atomics.store(cell, 0, 3);const old = Atomics.compareExchange(cell, 0, 3, 8);console.log(old, Atomics.load(cell, 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.

The replay shows the case where the check passes. Two values matter here, and they have different jobs:

  • The old value, 3, is what the call gives back.
  • The cell's new value, 8, is what stays in memory.

If the old value is not what you expected, another worker changed the cell first. Then you read again and try again.

Try again after another worker changes the cellPop out in the code editor (opens in a new tab)JavaScript
const score = new Int32Array(new ArrayBuffer(4));Atomics.store(score, 0, 3);let seen = Atomics.load(score, 0);while (Atomics.compareExchange(score, 0, seen, seen + 1) !== seen) {  seen = Atomics.load(score, 0);}console.log(Atomics.load(score, 0));

Line by line:

  1. Lines 1 and 2 make a cell holding 3.
  2. Line 3 reads the score and saves it in seen.
  3. Line 4 tries to replace seen with seen + 1. If the old value it gets back is not seen, another worker changed the cell first.
  4. In that case, line 5 reads the cell again, and the loop tries again.
  5. Here no other worker writes, so the first try works. Line 7 prints 4.

Keep these loops small. Use them for a quick change to one cell, not for long work. The next lesson, The memory model, explains the order rules that make this pattern safe.

Waiting for another worker

Adding to a counter happens right away. But sometimes a worker must pause until another agent changes a cell. For example, cell 0 could mean "is a job ready?"

A worker could keep reading the cell in a loop. That wastes work. Instead, Atomics.wait lets the worker sleep while the cell still holds a value you name. It wakes when another agent calls Atomics.notify.

A worker waits, then another agent wakes itJavaScript
// worker.js: never call Atomics.wait on the page's main thread.const cells = new Int32Array(sharedBuffer);const result = Atomics.wait(cells, 0, 0);console.log(result); // "ok" if woken, "not-equal" if the value already changed // the page or another workerAtomics.store(cells, 0, 1);Atomics.notify(cells, 0, 1);
Step one: the cell holds 0 and the worker sleeps. Step two: another agent stores 1. Step three: it notifies, and the worker wakes up with ok.0cells[0]worker sleeps1. store 11cells[0]value changed2. notifyokworker wakes
The worker sleeps while the cell holds 0. Another agent stores 1 first, then notifies, and the worker wakes with ok.

This is a sketch of two files. It can't run in the lesson editor. Line by line:

  1. Line 2 makes a view over the shared bytes. The worker got sharedBuffer in a message.
  2. Line 3 sleeps while cell 0 holds 0.
  3. Line 4 prints the result after the worker wakes.
  4. Line 7, in another agent, stores 1 first.
  5. Line 8 then calls notify to wake one waiting worker.

The result is one of three words:

  • "ok": the worker was asleep and got woken up.
  • "not-equal": the cell had already changed before the worker started waiting, so it did not sleep at all.
  • "timed-out": you gave a time limit, and it ran out before anyone woke the worker.

notify does not write a value. It only wakes workers. So always store the new value first, then notify.

The main thread is the page's own agent. It also draws the page and handles clicks. If it slept, the page would freeze, so browsers don't allow Atomics.wait there. Use Atomics.waitAsync instead. It does not sleep.

Wait without freezing the pageJavaScript
const cells = new Int32Array(sharedBuffer);const result = Atomics.waitAsync(cells, 0, 0);if (result.async) {  result.value.then((status) => console.log(status));} else {  console.log(result.value); // "not-equal" or "timed-out"}

Line by line:

  1. Line 2 calls waitAsync. It gives back an object right away instead of sleeping.
  2. If result.async is true, line 4 gets a promise. Later it prints "ok" when woken, or "timed-out" if a time limit runs out.
  3. Otherwise, line 6 prints the answer right away: "not-equal", or "timed-out" when the time limit is zero.

The table compares the three tools. Read a row as: the tool, what it does, and where you may use it.

Three tools for waiting on a cell
ToolWhat it doesWhere it belongs
Atomics.waitPuts the agent to sleep while the cell holds a value you name.In a worker only. The page's main thread may not sleep.
Atomics.notifyWakes agents that sleep on the cell. It gives back how many woke.In any agent, right after a store.
Atomics.waitAsyncGives back an answer now, or a promise of an answer later.Anywhere, including the main thread, because it never sleeps.

Give each cell value a meaning. Here, 0 means "no job" and 1 means "job ready". The store sets the meaning. The notify tells a sleeping worker to look again. Waking up does not prove the value is still the one the worker wanted, so it should read the cell again.

The browser's safety rule: cross-origin isolation

A browser lets a page make and send shared buffers only if the page is cross-origin isolated. That means the page is walled off from pages and files of other sites, unless they agree to it.

Why? Shared memory can be used as a very exact timer. Attackers can use exact timers to read secrets they should not see. One known attack of this kind is called Spectre. So the page has to opt in to a stronger wall first.

The page opts in with two response headers. A response header is a short line of text the server sends along with the page.

Two headers, then a check in codeHTTP
Cross-Origin-Opener-Policy: same-originCross-Origin-Embedder-Policy: require-corp if (crossOriginIsolated) {  const bytes = new SharedArrayBuffer(16);  console.log(bytes.byteLength);}

Line by line:

  1. Line 1 sets Cross-Origin-Opener-Policy (COOP) to same-origin. Windows from other sites that open this page, or that it opens, lose their link to it.
  2. Line 2 sets Cross-Origin-Embedder-Policy (COEP) to require-corp. The page may only load files from other sites that agree to it. Another allowed value is credentialless.
  3. Line 4 checks crossOriginIsolated. It is true only when the browser accepted both headers.
  4. Lines 5 and 6 make a shared buffer of 16 bytes and print 16.

COEP changes how files from other sites load on your page. So it is a decision for the whole site, not one worker. Before you turn it on, check every script, image, frame, and API your page loads from other sites. The browser security model lesson explains COOP and COEP in more depth.

A real use: counting finished jobs

A good real design stays small. Here is one. A group of workers gets jobs through messages. When a worker finishes a job, it adds one to a shared counter cell. The page reads that one cell to show progress. It does not need to ask every worker for a status message.

Two workers finish two jobsJavaScript
const progress = new Int32Array(new SharedArrayBuffer(4)); function finishJob() {  Atomics.add(progress, 0, 1);} finishJob(); // worker one finishesfinishJob(); // worker two finishesconsole.log(Atomics.load(progress, 0));

Line by line:

  1. Line 1 makes one shared cell for progress.
  2. Lines 3 to 5 are what a worker does when a job is done: add 1 to the cell.
  3. Lines 7 and 8 stand in for two workers, each finishing a job.
  4. Line 9 reads the cell and prints 2.

This model runs in a single agent, and you can run it in Node.js. In a real app, each worker gets the buffer in its first message. In a browser, the page must be cross-origin isolated. In Node.js, you pass the buffer to each worker through worker_threads. The lesson editor has no shared buffer, so this code can't run there.

Give each cell one job, and write down who writes to it. For example:

  • cell 0: how many jobs are done
  • cell 1: how many jobs there are in total
  • cell 2: a "ready" flag

Store a new value before you notify a worker. Let normal messages carry names, errors, and large results.

What job does each shared-memory tool do?
  • SharedArrayBuffer
  • Atomics.add
  • Atomics.compareExchange
  • Atomics.wait
  • Atomics.notify
  • Cross-Origin-Embedder-Policy
Try it yourself
0 of 6 correct

Sort each card: does it share or change memory, help agents wait for each other, or set up the browser?

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

In Node.js, two real workers can each add 1 one thousand times to one shared counter. The count always ends at exactly 2000. The lesson's tests check this. They also check the compare-and-swap values and a real wait and notify. They don't rely on a lost update showing up, because that depends on luck.

Common misconceptions

Shared memory does a small job on purpose. Most mistakes come from two wrong ideas. One is that it shares objects. The other is that one safe cell update makes the whole app safe. Keep your list of cells short enough that someone else can explain what each one is for.

  • "SharedArrayBuffer shares all my variables." No. Only the bytes inside the buffer are shared. Variables and objects stay private to each agent.
  • "Atomics makes any code safe." No. It makes one cell update safe. You still have to plan what each cell means and in what order agents use them.
  • "Atomics.wait is like await on the main thread." No. wait puts the agent to sleep, so it is for workers. waitAsync does not sleep, so the page can use it.
  • "notify changes the value." No. It only wakes workers. Store the new value first, then notify.
  • "COOP and COEP are optional." No. A browser page can't share memory without cross-origin isolation, and that needs both headers.

Messages are still the safe first choice. Use shared memory only when planning and measuring show a small shared-number problem. Some examples: a counter for a group of workers, a "ready" flag, or memory shared with WebAssembly.

Practice exercises

Each task is about one tool or one browser rule. For code, follow the value in the cell line by line before you answer.

Exercise 1 · Warm-upPredict the tiny counter

Read the program and type what it prints.

Starter codePop out in the code editor (opens in a new tab)JavaScript
const counter = new Int32Array(new ArrayBuffer(4));
Atomics.add(counter, 0, 5);
console.log(Atomics.load(counter, 0));

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

    Exercise 2 · Warm-upRead a compare and swap that does not match

    Type the two values this program prints.

    Starter codePop out in the code editor (opens in a new tab)JavaScript
    const cell = new Int32Array(new ArrayBuffer(4));
    Atomics.store(cell, 0, 4);
    console.log(Atomics.compareExchange(cell, 0, 3, 9));
    console.log(Atomics.load(cell, 0));

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

      Exercise 3 · PracticeFind the lost update

      In the playground, pick the order where both workers read first. What final count does the model show?

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

        Exercise 4 · PracticeWhere can a worker sleep?

        In a browser app, where may you use Atomics.wait, which puts the agent to sleep?

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

          Exercise 5 · ChallengeTurn on the browser safety rule

          Which two response headers turn on cross-origin isolation?

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

            Exercise 6 · ChallengeCount finished jobs in a real app

            Several workers share one counter of finished jobs. Which Atomics tool should each worker call when it finishes a job?

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

              Check your understanding

              For each question, first ask what it is about. Is it the shared bytes, one atomic tool, waiting, or the browser setup? Then follow the cell's value.

              Shared memory and Atomics quiz · 8 questionsScore: first tries count
              1. Question 1 of 8What does a SharedArrayBuffer share?

                Choose an answer to see the explanation.

              2. Question 2 of 8What does this print?

                Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
                const counter = new Int32Array(new ArrayBuffer(4));
                Atomics.add(counter, 0, 5);
                console.log(Atomics.load(counter, 0));

                Choose an answer to see the explanation.

              3. Question 3 of 8What does Atomics.add give back?

                Choose an answer to see the explanation.

              4. Question 4 of 8When does compare and swap put in the new value?

                Choose an answer to see the explanation.

              5. Question 5 of 8Why can't the page's main thread use Atomics.wait?

                Choose an answer to see the explanation.

              6. Question 6 of 8Which headers normally make a page cross-origin isolated?

                Choose an answer to see the explanation.

              7. Question 7 of 8A worker sends { score: 3 } with a normal postMessage. Later it changes its own object. What does the receiver have?

                Choose an answer to see the explanation.

              8. Question 8 of 8A worker expects a cell to hold 0, but it already holds 1. What can waitAsync give back right away?

                Choose an answer to see the explanation.

              Key takeaways

              • A SharedArrayBuffer shares bytes. Variables, objects, and call stacks stay private to each agent.
              • Read shared bytes as number cells through a view such as Int32Array.
              • When several agents change one cell, use Atomics, so each change is one step that can't be split.
              • compareExchange changes a cell only if it still holds the value you expect.
              • wait and notify let a worker sleep and wake. waitAsync waits without freezing the page.
              • A browser page needs COOP and COEP, and crossOriginIsolated must be true.

              Remember the one-liner.
              Shared memory shares bytes; Atomics gives each shared number cell a safe, one-step way to change.

              Coming next: The memory model. It explains when one agent sees another agent's writes, and in what order.

              CompleteFrontend Clear concepts. Working examples.