completefrontend
Open lab My progress
THE JAVASCRIPT FIELD GUIDE

JavaScript & WebAssembly

Learn when WebAssembly helps, how JavaScript loads and calls it, how memory is shared, and how modern Wasm features fit together.

By the end, you can
  • 01
    Load a moduleCheck Wasm bytes, load a small module, and call its function from JavaScript.
  • 02
    Explain how the two sides talkSay what imports, exports, and shared memory are, and why an old view has 0 bytes after the memory grows.
  • 03
    Know when Wasm fitsSay when Wasm helps, what V8's two compilers do, and what WasmGC and JSPI add, without treating Wasm as a JavaScript replacement.

What WebAssembly is

WebAssembly, or Wasm for short, is a kind of program that browsers can run next to JavaScript. It is stored as binary: plain numbers (bytes), not text you can read.

People rarely write Wasm by hand. They write code in another language, such as C, C++, or Rust. Then a compiler, a tool that translates code, turns it into Wasm. JavaScript then loads the Wasm and calls it.

Wasm works with JavaScript. It does not replace it. It helps when a small part of an app does the same number work again and again. For example:

  • resizing an image
  • decoding video or sound
  • running a game loop
  • doing big calculations

Page layout, buttons, forms, and network requests stay in JavaScript. The browser's tools for these jobs are made for JavaScript.

A Wasm file is called a module. Before a module runs, the browser checks that its bytes are well formed. The module can only use what JavaScript hands it. The same module runs on any computer, and the browser still turns it into fast code for that computer.

Do not pick Wasm just because a job sounds hard. First profile the page: measure where the time really goes. Often the slow part is a page update or a wait for the network, and Wasm cannot speed those up. A short calculation that runs many times is a better fit.

Definition

Wasm is a binary program format that runs the same way on every computer. JavaScript loads a Wasm module and calls the functions the module offers. The two can also share one block of bytes, called linear memory.

The table below compares the two. Each row asks one question and answers it for JavaScript and for Wasm.

JavaScript and Wasm do different jobs
QuestionJavaScriptWebAssembly
What is it best at?Using the browser's tools and running the app.Doing one piece of number work fast.
How is it usually written?People write and keep the code by hand.A compiler makes it from another language.
What can it pass to the other side?Objects, typed arrays, and numbers.Numbers, and bytes in shared memory.
What should you ask first?Which user action needs work?Did measuring show a short job that repeats a lot?
Real-life analogyA home cook and a food processor

A home cook can make a whole meal. A food processor chops very fast, but only chops. The cook still plugs it in, puts the food in, and serves the result.

In real life: The cook decides what to make
In JavaScript: JavaScript runs the app
In real life: The food processor does one job again and again
In JavaScript: Wasm does one piece of number work
In real life: The cook plugs it in and puts food in
In JavaScript: JavaScript loads the module and calls it
In real life: The cook serves the result
In JavaScript: JavaScript uses the value Wasm returns

This is the last lesson in the runtime stage. The next stage starts with How to read ECMAScript. The earlier typed arrays lesson helps here. Typed arrays also show raw bytes as numbers, and Wasm uses them a lot.

A tiny Wasm module

The easiest first module holds one function that adds two whole numbers. The bytes below are a complete module. Here is what they hold, in order:

  • a start mark, \0asm, that says "this is Wasm", and a version number
  • the shape of the function: two numbers in, one number out
  • one function with that shape
  • an export: a public name, add, that JavaScript can call
  • the steps that read the two numbers and add them
Load a Wasm add module and call itPop out in the code editor (opens in a new tab)JavaScript
const bytes = new Uint8Array([0,97,115,109,1,0,0,0,1,7,1,96,2,127,127,1,127,3,2,1,0,7,7,1,3,97,100,100,0,0,10,9,1,7,0,32,0,32,1,106,11]);const { instance } = await WebAssembly.instantiate(bytes);console.log(instance.exports.add(2, 3));

Uint8Array is a typed array: a list where each item is one byte, a whole number from 0 to 255.

  1. Line 1 puts the 41 module bytes in a Uint8Array.
  2. Line 2 calls WebAssembly.instantiate. It checks the bytes, turns them into machine code, and makes an instance: a ready-to-use copy of the module.
  3. Line 3 calls the exported add function through instance.exports. It prints 5.
Line 1 holds 41 bytes. Line 2 turns them into a Wasm instance that holds an add function. Line 3 calls add with 2 and 3 and gets 5.41 bytes1. the bytesadd()Wasm2. an instance53. add(2, 3)
The 41 bytes become an instance, a ready-to-use copy of the module. Calling its add function with 2 and 3 gives 5.

The first 8 bytes are the start mark and the version. Each part after that is called a section. A section starts with:

  • an ID byte that says which kind of section it is
  • a length that says how many bytes follow

Because of the length, the browser can jump over a section it does not need yet.

Name the five parts of the add modulePop out in the code editor (opens in a new tab)JavaScript
const bytes = new Uint8Array([0,97,115,109,1,0,0,0,1,7,1,96,2,127,127,1,127,3,2,1,0,7,7,1,3,97,100,100,0,0,10,9,1,7,0,32,0,32,1,106,11]);const sections = [  ["header and version", 0, 8],  ["type", 8, 17],  ["function", 17, 21],  ["export", 21, 30],  ["code", 30, 41],]; for (const [name, start, end] of sections) {  console.log(name, bytes.slice(start, end).length);}

Line 1 holds the same 41 bytes. Lines 3 to 7 give each part a name, a start position, and an end position:

  • header and version
  • type: the function's shape
  • function: which shape the function uses
  • export: the public name
  • code: the steps

The loop on lines 10 to 12 prints each name and its size. It prints header and version 8 first and code 11 last. These names only fit this one module. The code is not a tool that can read any Wasm file.

The 41 bytes of the add module in order: header and version, 8 bytes. Type, 9 bytes. Function, 4 bytes. Export, 9 bytes. Code, 11 bytes.the 41 bytes, in orderheader + version8 bytestype9 bytesfunction4 bytesexport9 bytescode11 bytes
The add module has 5 parts, one after another. Their sizes, 8 + 9 + 4 + 9 + 11, add up to all 41 bytes.

You will almost never write these bytes yourself. A compiler writes them. People can read Wasm in a text form called WAT (WebAssembly Text). Below is the same add function in WAT:

  • i32 means a 32-bit whole number.
  • local.get 0 and local.get 1 read the two inputs.
  • i32.add adds them.
  • The last line exports the function under the name add.
The same add function in WebAssembly textWebAssembly text
(module
  (func $add (param i32 i32) (result i32)
    local.get 0
    local.get 1
    i32.add)
  (export "add" (func $add)))
Step through: call the add function from Wasm
Step 0 of 4Ready
Your turn: follow the blue line

This replays the lesson's real call to the add module. It is not a tool that looks inside the engine.

Running in
  1. script
Next: line 1
const { instance } = await WebAssembly.instantiate(bytes);console.log(instance.exports.add(2, 3));
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 five parts of the add module
Step 0 of 5Ready
Your turn: follow the blue line

This walks through the parts of this one add module. It is not a tool that can read any Wasm file.

Running in
  1. script
Next: line 3
const bytes = new Uint8Array([0,97,115,109,1,0,0,0,1,7,1,96,2,127,127,1,127,3,2,1,0,7,7,1,3,97,100,100,0,0,10,9,1,7,0,32,0,32,1,106,11]);const sections = [  ["type", 8, 17],  ["function", 17, 21],  ["export", 21, 30],  ["code", 30, 41],]; for (const [name, start, end] of sections) {  console.log(name, bytes.slice(start, end).length);}
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.

Loading and calling modules

Three words come up again and again when you load Wasm:

  • A module is the Wasm code, checked and turned into machine code. On its own, it cannot run yet.
  • An instance is a module that is ready to run. It has its own memory and the extra things JavaScript handed it.
  • Instantiation is the step that makes an instance from a module.

An instance offers its exports to JavaScript. Exports can be:

  • functions
  • memories (blocks of bytes)
  • tables: lists that hold things such as functions, not bytes
  • globals: single values

Wasm functions only take and return a few kinds of values:

  • i32 and i64 are whole numbers (32 and 64 bits).
  • f32 and f64 are numbers with decimals.
  • Reference types point at things, such as functions, instead of holding bytes.

When an i64 value goes to or from JavaScript, it is a BigInt, not a normal number.

If you get bytes from somewhere you do not trust, check them first. WebAssembly.validate answers true or false: are these bytes a well-formed Wasm module?

Check good and broken Wasm bytesPop out in the code editor (opens in a new tab)JavaScript
const bytes = new Uint8Array([0,97,115,109,1,0,0,0,1,7,1,96,2,127,127,1,127,3,2,1,0,7,7,1,3,97,100,100,0,0,10,9,1,7,0,32,0,32,1,106,11]);const broken = new Uint8Array([0, 97, 115, 109]);console.log(WebAssembly.validate(bytes));console.log(WebAssembly.validate(broken));
  1. Line 1 holds the good add module.
  2. Line 2 holds only the first 4 bytes, the start mark.
  3. Line 3 prints true.
  4. Line 4 prints false.

validate does not run the module. It also cannot tell you if the module does anything useful. It only checks the format.

The table below lists the ways to load Wasm. Each row is one tool: what it does, and when to use it.

Ways to load and call Wasm
When you writeWhat it doesWhen to use it
WebAssembly.validate(bytes)Checks that the bytes are a well-formed module. Answers true or false.Before you load bytes you do not trust. It does not make an instance.
WebAssembly.instantiate(bytes)Turns the bytes into machine code and makes an instance.When you already have the bytes, for example in a Uint8Array.
WebAssembly.instantiateStreaming(fetch(url))Starts making the instance while the file is still downloading.When a server sends a .wasm file labelled application/wasm.
instance.exports.add(2, 3)Calls a function that the module offers to JavaScript.After the instance is ready, to use the module.

Wasm usually comes from a file on a server. With streaming, the browser starts turning the file into machine code while it is still downloading. For this to work, the server must label the file as application/wasm. That label is called the MIME type. This example needs a real file, so you cannot run it here.

Load a Wasm file while it downloadsJavaScript
const { instance } = await WebAssembly.instantiateStreaming(
  fetch("add.wasm"),
);
console.log(instance.exports.add(2, 3));
  1. Lines 1 and 2 start a download with fetch and pass it to instantiateStreaming. await waits until the instance is ready.
  2. Line 4 calls the exported add function.

Things can also go the other way. JavaScript can hand functions or memory to the module. These are called imports. You pass them as the second argument to instantiate. Then Wasm code can call those JavaScript functions.

Liftoff and TurboFan: two compilers

Wasm bytes are not the machine code of your computer. The browser must turn them into machine code first. This lesson looks at V8, the engine inside Chrome and Node.js. V8 has two compilers for Wasm:

  • Liftoff makes machine code very quickly, so the app can start soon. Its code is simple and not the fastest.
  • TurboFan takes more time, but makes faster machine code.

Using a quick compiler first and a better one later is called tiered compilation. Each compiler is one tier. The code below is a small make-believe model of that choice.

A small model: pick a compiler by number of callsPop out in the code editor (opens in a new tab)JavaScript
function chooseTier(calls) {  return calls >= 3 ? "TurboFan" : "Liftoff";} console.log(chooseTier(1));console.log(chooseTier(3));
  1. Line 1 takes calls: how many times a function has been called.
  2. Line 2 answers "Liftoff" for fewer than 3 calls and "TurboFan" for 3 or more.
  3. Lines 5 and 6 print Liftoff and then TurboFan.

The number 3 is made up for the lesson. Real engines use their own rules to decide when to switch.

The steps from not called to TurboFanPop out in the code editor (opens in a new tab)JavaScript
function describeTier(calls) {  if (calls === 0) return "not called";  if (calls < 3) return "Liftoff baseline";  return "TurboFan candidate";} console.log(describeTier(0));console.log(describeTier(1));console.log(describeTier(3));
  1. Line 2 says "not called" when the function has not run yet.
  2. Line 3 says "Liftoff baseline" for the first few calls. A baseline is the simple first version of the code.
  3. Line 4 says "TurboFan candidate" for later calls. A candidate is a function that may get the faster code.
  4. Lines 7 to 9 print the three steps in order.

Here is what V8's own documents say happens:

  • When a Wasm function is first called, Liftoff compiles it.
  • If the function runs a lot (it is hot), TurboFan compiles it again in the background.
  • TurboFan works harder. For example, it keeps values in the fast spots of the processor, removes repeated work, and copies small functions into the places that call them.
  • A call that is already running on Liftoff code finishes on Liftoff code. Later calls can use the TurboFan code.

The tiered compilation lesson shows the same idea for JavaScript code.

Wasm code that is not called yet. On the first calls, Liftoff makes machine code quickly. If the function stays busy, TurboFan makes faster machine code for later calls.first callsif it stays busyWasm codeLiftoffTurboFannot called yetquick to makefaster to run
In V8, Liftoff makes machine code quickly, so the app can start soon. If a function keeps running a lot, TurboFan makes a faster version for later calls.
Two compilers, one goal

Liftoff helps the app start fast. TurboFan helps busy code run fast. The app author does not change anything: the switch happens by itself.

Sharing linear memory

Linear memory is one long row of bytes, numbered from 0. Wasm keeps its data there. There are two ways to set it up:

  • JavaScript makes the memory and hands it to the module.
  • The module makes the memory and exports it.

Either way, JavaScript reads and writes the bytes through a view: a typed array such as Uint8Array laid over the memory. Both sides see the very same bytes. Nothing is copied.

Memory comes in pages. One page is 65536 bytes (64 KiB). memory.buffer is the block of bytes, and byteLength is its size in bytes.

Make Wasm memory, write a byte, and make it biggerPop out in the code editor (opens in a new tab)JavaScript
const memory = new WebAssembly.Memory({ initial: 1 });const oldView = new Uint8Array(memory.buffer);oldView[12] = 42;console.log(memory.buffer.byteLength, oldView[12]);memory.grow(1);console.log(memory.buffer.byteLength, oldView.byteLength);
  1. Line 1 makes a memory with one page.
  2. Line 2 makes a view, oldView, over that memory.
  3. Line 3 writes 42 into byte number 12.
  4. Line 4 prints 65536 42: one page, and the byte we wrote.
  5. Line 5 grows the memory by one page with grow(1).
  6. Line 6 prints 131072 0. The memory now has two pages. But oldView now has 0 bytes.

Why 0? Growing swaps in a new, bigger block of bytes. The old view still points at the old block, which is now empty and unusable. We say the old view is detached.

Left, before grow(1): the oldView tag points at the memory box, which has 65536 bytes. Right, after grow(1): the memory box has 131072 bytes, and the oldView tag points at nothing and now shows 0 bytes.before grow(1)oldViewmemory65536 bytesafter grow(1)oldViewnow 0 bytesmemory131072 bytes
Growing the memory swaps in a new, bigger buffer. The old view is left with 0 bytes, so make a new view after grow.

So after grow, always make a fresh view from memory.buffer.

Some numbers take more than one byte. The order of those bytes matters. Wasm memory always stores the small end first: the low byte comes before the high byte. This order is called little-endian. A DataView is a view that reads and writes numbers of any size. You pass true to tell it to use little-endian order.

Store 500 in two bytes, low byte firstPop out in the code editor (opens in a new tab)JavaScript
const memory = new WebAssembly.Memory({ initial: 1 });const view = new DataView(memory.buffer); view.setUint16(0, 500, true);console.log(view.getUint8(0), view.getUint8(1));console.log(view.getUint16(0, true));
  1. Line 1 makes the memory.
  2. Line 2 makes a DataView over it.
  3. Line 4 writes 500 into bytes 0 and 1, low byte first.
  4. Line 5 reads each byte alone and prints 244 1.
  5. Line 6 reads both bytes as one number and prints 500.

Why 244 and 1? 500 is 1 × 256 + 244. So the low byte is 244 and the high byte is 1.

Wasm can also run on several threads at once. Then the memory must be shared, using a SharedArrayBuffer, and the threads must take turns with atomics (safe, one-at-a-time operations). On the web, a page only gets SharedArrayBuffer when it is cross-origin isolated, a special security setting. The editor on this page does not have that setting.

Real-life analogyOne cutting board

Two cooks share one cutting board. They do not pass food back and forth. Each one just reaches for the same board.

In real life: Two cooks use one cutting board
In JavaScript: JavaScript and Wasm use one memory
In real life: A cook puts a piece at one spot on the board
In JavaScript: A view writes a byte at one position
In real life: Both cooks see the same piece
In JavaScript: Both sides read the same bytes
In real life: A bigger board takes the old board's place
In JavaScript: Growing memory leaves old views with 0 bytes
Step through: grow the memory by one page
Step 0 of 5Ready
Your turn: follow the blue line

This replays the lesson's real memory code. It shows what JavaScript can see, not how the engine stores the bytes inside.

Running in
  1. script
Next: line 1
const oldView = new Uint8Array(memory.buffer);oldView[12] = 42;console.log(memory.buffer.byteLength, oldView[12]);memory.grow(1);console.log(memory.buffer.byteLength, oldView.byteLength);
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.
Playground: write one byte, then grow the memory
The memory code from the lessonJavaScript
const memory = new WebAssembly.Memory({ initial: 1 });const oldView = new Uint8Array(memory.buffer);oldView[12] = 42;console.log(memory.buffer.byteLength, oldView[12]);memory.grow(1);console.log(memory.buffer.byteLength, oldView.byteLength);
What JavaScript sees65536 bytes at first
byte 1242

JavaScript wrote this byte through a Uint8Array view.

size after grow131072

One more page adds 65536 bytes.

old view byteLength0

The old view has 0 bytes. Make a new view after grow.

Try it yourself

Byte 12 now holds 42. One page is 65536 bytes. After grow, the memory has 131072 bytes, and the old view has 0 bytes.

This runs the lesson's real memory code with a real WebAssembly.Memory. It shows what JavaScript sees.

WasmGC: the browser cleans up Wasm objects

Some languages, such as Java, Kotlin, and Dart, use a garbage collector. That is a helper that finds objects nobody uses any more and frees their memory. JavaScript has one too.

Plain Wasm only has linear memory: bytes, with no idea of objects. So these languages used to pack their own garbage collector into every Wasm file. That makes the file bigger.

WasmGC fixes this. It adds two kinds of objects to Wasm:

  • structs: objects with a fixed set of fields
  • arrays: lists of values

The browser's own garbage collector looks after these objects. So the language no longer needs to ship its own. The files get smaller, and the browser knows the shape of each object.

You do not use WasmGC in your JavaScript code. It is something the compiler for a language uses when it makes Wasm. The language needs a compiler that knows how to write WasmGC.

V8 says WasmGC shipped in Chrome 119. Other browsers can differ, so check a browser support table before you rely on it.

A page can first check that the browser has the basic Wasm tools at all. This is called feature detection.

Check that the browser has the basic Wasm toolsPop out in the code editor (opens in a new tab)JavaScript
const canUseWasm = typeof WebAssembly === "object" &&  typeof WebAssembly.instantiate === "function"; console.log(canUseWasm);console.log(typeof WebAssembly.Memory === "function");
  1. Lines 1 and 2 check that WebAssembly exists and that it has an instantiate function. typeof gives the kind of a value as text, such as "object" or "function".
  2. Line 4 prints the answer: true.
  3. Line 5 checks that WebAssembly.Memory is a function. It prints true too.

This check only says the basic tools are there. It does not say that WasmGC or other newer features work. A real app should also test the exact module it plans to load.

What WasmGC does not do

WasmGC does not turn your JavaScript objects into Wasm objects. It gives compilers new object types for Wasm, and the browser cleans those up.

JSPI: Wasm waits for a Promise

Many browser tools, such as fetch, return a Promise: a value that will be ready later. But code written in C or Rust usually expects an answer right away. It does not know how to wait for a Promise.

JavaScript Promise Integration, or JSPI, solves this. Here is what happens:

  1. Wasm calls a JavaScript function that it imported.
  2. That function returns a Promise.
  3. The Wasm code pauses.
  4. When the Promise is ready, the Wasm code goes on.
A function that returns a Promise, the kind JSPI waits forPop out in the code editor (opens in a new tab)JavaScript
// Only an example: a Wasm compiler usually sets up JSPI for you.async function askForPrice() {  return Promise.resolve(12);} console.log(await askForPrice());
  1. Line 1 is a comment: this is only an example. A Wasm compiler usually sets up JSPI for you.
  2. Line 2 starts an async function. It returns a Promise that becomes 12.
  3. Line 5 waits with await and prints 12.

There is no Wasm in this code. It only shows the kind of JavaScript function JSPI works with. The compiler wraps the module's imports and exports when it loads. Then Wasm code that looks like it gets answers right away can pause and go on later.

The official Wasm feature list says JSPI is now part of the standard. But each browser adds it in its own version, so test it in the browsers you need.

JSPI only pauses Wasm code. It does not pause normal JavaScript. JavaScript already has async, await, and Promises for that.

Choosing Wasm for a real app

Start with a problem the user can feel, and measure it. Say a photo editor is slow when it resizes images:

  • Profile the resize loop.
  • If the loop takes most of the time, and it only works on numbers, it may be a good fit for Wasm.
  • If a click is slow because of the network or because the page is redrawing, Wasm will not help.

The place where JavaScript and Wasm hand data to each other is called the boundary. Keep it small at first:

  • Pass one typed array, or a few numbers.
  • Call one export.
  • Measure before and after.

Every crossing costs a little time. Turning big JavaScript objects into bytes, or copying large arrays, again and again can cost more than Wasm saves.

Resize an image with one typed array in and one outPop out in the code editor (opens in a new tab)JavaScript
function resizeNearest(pixels, sourceWidth, targetWidth) {  const result = new Uint8Array(targetWidth);   for (let index = 0; index < targetWidth; index += 1) {    const sourceIndex = Math.floor(index * sourceWidth / targetWidth);    result[index] = pixels[sourceIndex];  }   return result;} const pixels = new Uint8Array([10, 20, 30, 40]);console.log([...resizeNearest(pixels, 4, 2)].join(","));

This shrinks a row of 4 pixels to 2 pixels. Each pixel is one byte.

  1. Line 1 is the kind of small function a Wasm module could export. It takes the pixels, the old width, and the new width.
  2. Line 2 makes a new typed array for the result.
  3. Lines 4 to 6 loop over the new pixels. For each one, they pick the nearest old pixel and copy it.
  4. Line 9 returns the result.
  5. Line 13 prints 10,30: pixels 0 and 2 of the old row.

This code is JavaScript so that it can run here. The point is the shape of the boundary:

  • Pass the pixel bytes in once.
  • Do all the repeated math together.
  • Pass the result bytes back once.

The buttons and the preview stay in JavaScript. Only move the loop to Wasm after profiling shows it is what makes the user wait.

A few more Wasm words you may meet:

  • WASI gives Wasm programs outside the browser a way to use things like files. It is not a file system inside the browser.
  • SIMD lets one step work on several numbers at once.
  • Exception handling lets compiled code throw and catch errors.
  • The component model aims to let Wasm parts talk through richer types than numbers. It is still a proposal, and browsers do not have it.

Support for each of these differs, so check the places you plan to run your code.

The table below lists choices for a first boundary. Each row says what to do, an example, and why.

How to keep the first boundary small
What to decideExampleWhy
What to pass firstOne Uint8Array of pixels and two width numbers.One resize call gives back one array.
What to watch forTurning JavaScript objects into bytes on every call.It can use up all the time Wasm saves.
What to keep in JavaScriptButtons, the preview, and the browser's tools.They are made to be used from JavaScript.
Where does this part belong?
  • fetch("image.wasm")
  • The steps inside the exported add function
  • WebAssembly.instantiate(bytes)
  • new Uint8Array(memory.buffer)
  • Changing the text on a button
  • An i32.add step
Try it yourself
0 of 6 correct

Put each card on the JavaScript side, the Wasm side, or the boundary between them.

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

To run heavy work on several threads, see shared memory and parallel patterns. Those lessons explain how workers take turns. This lesson is about how JavaScript and Wasm pass data to each other.

Common misconceptions

  • “Wasm replaces JavaScript.” No. Usually JavaScript calls Wasm for one focused job.
  • “Wasm is always faster.” Not always. Passing data across the boundary takes time, the file must download, and the slow part may not be number work at all.
  • “Wasm memory holds objects.” No. Linear memory is only bytes. Both sides must agree on which byte means what.
  • “Every browser uses Liftoff.” No. Liftoff is the name of V8's compiler. Other engines have their own.
  • “JSPI replaces async JavaScript.” No. JSPI only lets Wasm wait for a Promise that JavaScript returns.

The table below lists words that are easy to mix up. Each row says what the word means and what it does not mean.

Wasm words that are easy to mix up
WordWhat it isWhat it is not
WasmA binary program format that a compiler makes and JavaScript runs.A language you write by hand, or a JavaScript replacement.
Linear memoryOne row of bytes that Wasm and JavaScript both read and write.JavaScript objects shared field by field.
LiftoffV8's quick first compiler for Wasm.A compiler that every browser uses.
JSPIA way for Wasm to pause until a JavaScript Promise is ready.A way to pause normal JavaScript code.

Practice exercises

Exercise 1 · Warm-upPredict what the add program prints

Read the small program and type what it prints.

Starter codePop out in the code editor (opens in a new tab)JavaScript
const add = (left, right) => left + right;
console.log(add(2, 3));

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

    Exercise 2 · Warm-upPick the tool that checks the bytes

    Someone sent you some bytes. Before you load them, you want to check that they are a well-formed Wasm module. Which function do you call?

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

      Exercise 3 · PracticeHow big is one page?

      This program makes a memory with one page. What does it print?

      Starter codePop out in the code editor (opens in a new tab)JavaScript
      const memory = new WebAssembly.Memory({ initial: 1 });
      console.log(memory.buffer.byteLength);

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

        Exercise 4 · PracticeSpot the old view

        A teammate grows Wasm memory with grow(1). Then they keep using the view they made before. Which byteLength number shows that the old view is detached?

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

          Exercise 5 · ChallengeUse Wasm in a photo editor

          A photo editor freezes for a moment while it resizes large images. Which part would you try moving to Wasm first?

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

            Exercise 6 · ChallengeWhat does JSPI wait for?

            Wasm calls a JavaScript function that works in the background. JSPI pauses the Wasm code. What is it waiting for?

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

              Check your understanding

              For each question, ask: is this a JavaScript job, a Wasm job, or the boundary between them? Who loads the module, who does the number work, and which bytes do they share?

              JavaScript & WebAssembly quiz · 8 questionsScore: first tries count
              1. Question 1 of 8What is WebAssembly mainly for?

                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 add = (left, right) => left + right;
                console.log(add(2, 3));

                Choose an answer to see the explanation.

              3. Question 3 of 8Which tool only checks that bytes are a well-formed module?

                Choose an answer to see the explanation.

              4. Question 4 of 8Wasm memory grows with grow(1). What happens to a Uint8Array view made before that?

                Choose an answer to see the explanation.

              5. Question 5 of 8Why does V8 use Liftoff before TurboFan for Wasm?

                Choose an answer to see the explanation.

              6. Question 6 of 8What does WasmGC give to languages such as Java, Kotlin, and Dart?

                Choose an answer to see the explanation.

              7. Question 7 of 8What problem does JSPI (JavaScript Promise Integration) solve?

                Choose an answer to see the explanation.

              8. Question 8 of 8An image editor wants to try Wasm. What is the best first step?

                Choose an answer to see the explanation.

              Key takeaways

              • Wasm is a binary program format. A compiler makes it, and JavaScript loads it and calls it for one focused job.
              • validate checks the bytes. instantiate loads bytes you already have. instantiateStreaming loads a file while it downloads.
              • V8 uses Liftoff to make machine code quickly, and TurboFan to make faster code for busy functions.
              • Linear memory is one row of bytes that both sides share. After grow, make a new view.
              • WasmGC lets languages such as Java, Kotlin, and Dart use the browser's garbage collector for their Wasm objects.
              • JSPI lets Wasm code pause while it waits for a JavaScript Promise.

              Remember the one-liner.
              JavaScript runs the app. Wasm is a fast helper for number work that JavaScript loads, calls, and shares bytes with.

              Coming next: How to read ECMAScript

              CompleteFrontend Clear concepts. Working examples.