completefrontend
Open lab My progress
THE JAVASCRIPT FIELD GUIDE

Small & embedded engines

Learn how Hermes, QuickJS, XS, and engine262 choose startup, memory, speed, and specification clarity for different JavaScript jobs.

By the end, you can
  • 01
    Match an engine to a needExplain why a phone app, a microcontroller, a server, and spec testing each need a different kind of engine.
  • 02
    Explain prepared bytecodeDescribe how Hermes can make compact bytecode when the app is built, and why that helps a React Native app start fast.
  • 03
    Choose by purpose, not only speedCompare QuickJS, XS, and engine262 without calling one engine the best for every job.

Why make a small engine?

A JavaScript engine is the program that reads your JavaScript and runs it. Browsers have very big engines, because they run the whole web. Some jobs need a smaller engine:

  • A phone app should open quickly.
  • A tiny device has very little memory.
  • A team that writes the language rules wants to see each rule clearly.
The main idea

A small engine is a JavaScript engine built for one main need, such as a fast start, low memory use, fitting inside another program, or checking the language rules. It still runs JavaScript. It just gives up some things to be good at its one need.

No engine is best for every job. A server runs for days, so it can wait for repeated code to get faster. A tiny device can't wait or spend extra memory. A team checking the language rules cares more about seeing each step than about speed.

Run a small function many timesPop out in the code editor (opens in a new tab)JavaScript
function addCartPrice(price, times) {  let total = 0;  for (let count = 0; count < times; count += 1) total += price;  return total;} console.log(addCartPrice(3, 4));

Line 1 makes a function called addCartPrice. Line 2 starts total at zero. Line 3 is a loop: it adds the price once for each item. Line 7 calls the function with a price of 3 and four items, so it prints 12.

Two kinds of engine run this code in different ways:

  • An interpreter runs the steps the same way every time. Its speed stays steady.
  • A JIT engine first watches the loop repeat. Then it spends extra work to make a faster version. This early extra work is called warm-up.

Neither one is better in general. The right choice depends on what the product needs most.

Real-life analogyA camping stove

A restaurant kitchen can cook many dishes at once. A camping stove is light. It is ready almost at once, and it fits in a backpack. It cooks a simple meal more slowly. That is fine, because on a hike you need something light, not a full kitchen.

In real life: A camping stove fits in a backpack
In JavaScript: A small engine fits a product with little room
In real life: It is ready to cook almost at once
In JavaScript: It starts quickly
In real life: It cooks one simple meal
In JavaScript: It does one main job well
In real life: A big kitchen cooks many dishes at once
In JavaScript: A big JIT engine makes long-running code fast

This lesson comes after Inside Deno, Bun & edge runtimes. That lesson compared runtimes. This one looks at the engines inside, and at what each engine chooses to keep or leave out.

A steady interpreter and a warming JIT

An interpreter reads the program one step at a time and does each step. A JIT compiler (JIT means "just in time") turns code that runs often into fast machine code while the program is running. Machine code is the language the computer's chip runs directly.

The same addition each callPop out in the code editor (opens in a new tab)JavaScript
function addPrice(price) {  return price + 1;}console.log(addPrice(3));

Line 1 makes a tiny function. Line 2 adds one to price. Line 4 calls it with 3, so it prints 4.

An interpreter does this addition the same way on every call. A JIT works in three stages:

  1. It first runs a plain, general version of the function.
  2. It watches many calls and notes facts, such as price is always a number.
  3. It then makes a faster version that uses those facts.

This helps code that runs many times. It also costs something: work at the start, memory for the new machine code, and memory for the notes.

So "fast to start" and "fast after a while" are two different things. Opening an app once is not like running a busy server all day. Always ask which moment the user feels.

The table below compares four kinds of engine. Read each row as one kind of engine. Each score goes from 1 to 10, and a bigger number is better for that column.

Made-up scores for four kinds of engine (a teaching model, not real measurements)
Kind of engineStart scoreSpeed after warm-up scoreMemory scoreWhere it fits best
JIT engine4108Server
AOT bytecode interpreter965Phone app
Small interpreter849Microcontroller with 256 KB of memory
Spec interpreter313Spec testing

These scores are made up on purpose. They give us simple words for comparing engines. They are not real times or real memory sizes.

Hermes and prepared bytecode

Hermes is a JavaScript engine made so that React Native apps start fast. React Native is a tool for building phone apps with JavaScript.

Before an engine can run JavaScript, it turns the text into bytecode. Bytecode is a list of small, simple instructions that the engine can run. Most engines make the bytecode when the app opens. Hermes can make it earlier, when the app is built. Doing work early like this is called ahead-of-time, or AOT. The Hermes project says it does two things:

  • It improves the code ahead of time, while the app is built.
  • It stores the result as compact (small) bytecode.
A label made before the app opensPop out in the code editor (opens in a new tab)JavaScript
const appBytecode = "prepared at build time"; function openApp() {  console.log(appBytecode);} openApp();

Line 1 is only a text label for this lesson. It is not real Hermes bytecode. Line 3 makes a function called openApp. Line 4 prints the label. Line 7 calls the function, so the code prints prepared at build time.

Your code, app.js, becomes bytecode at build time. Later the phone runs that bytecode when the app opens.app.jsyour codebytecodeat build timePhoneopenApp()when the app opens
The slow step, turning code into bytecode, happens once at build time. When the app opens, the phone starts from the bytecode.
Real-life analogyPre-chopped vegetables

Chopped vegetables let you start cooking sooner, because the cutting was done before dinner. Hermes does the same with bytecode. Turning JavaScript into bytecode can happen when the app is built, before anyone installs it.

In real life: The vegetables are chopped before dinner
In JavaScript: The bytecode is made when the app is built
In real life: Cooking starts sooner
In JavaScript: The app skips some work when it opens
In real life: The cook still follows the recipe steps
In JavaScript: The engine still runs the bytecode
In real life: A helper did the chopping, not the cook
In JavaScript: The build tools made the bytecode, not the phone

This does not mean "Hermes never compiles". The Hermes project only says it improves code ahead of time and uses compact bytecode. The exact details depend on the Hermes version and on how React Native uses it. To know how fast a real app opens, measure it with real tools.

You may also read about Static Hermes. It is a separate project that turns code into machine code. It is not the usual bytecode path that React Native uses. Check which version you use before you make claims about it.

The main idea is simple. Moving work from "when the app opens" to "when the app is built" can make the app open faster. The bytecode file you ship then becomes part of how the app runs.

QuickJS: a small engine inside other programs

QuickJS calls itself a small engine that is easy to embed. To embed an engine means to put it inside another program, so that program can run JavaScript too. The QuickJS project lists these features:

  • A fast interpreter.
  • A very quick start.
  • It turns JavaScript into bytecode before running it.
  • It frees memory with reference counting and cycle removal. Both are explained below.
Two names for one objectPop out in the code editor (opens in a new tab)JavaScript
const order = { item: "tea" };const anotherName = order; console.log(anotherName.item);

Line 1 makes one object and calls it order. Line 2 gives the same object a second name, anotherName. It does not copy the object. Line 4 reads the item through anotherName, so it prints tea.

Two variables, order and anotherName, both point to one object that holds item tea. The object's count is 2.orderanotherNameitem: "tea"objectcount: 2
order and anotherName point to the same object. Reference counting keeps a number on the object: how many names point to it. Here the number is 2.

A reference is a name or property that points to an object. Reference counting means the engine keeps a number on each object: how many references point to it. When a reference goes away, the number goes down. When it reaches zero, nothing can use the object, so the engine frees its memory.

A cycle is a problem for counting. Picture two objects that point to each other. Nothing else points to them, so your code can never use them again. But each one still has a count of 1, from the other object. The counts never reach zero.

That is why QuickJS also lists cycle removal: an extra step that finds such loops and frees them. Counting alone can't do it. To see the counting step by step, read Reference counting & cycles.

Embedding helps when a bigger program needs JavaScript for one small feature. There, a small size and a simple way to connect matter more than top speed after minutes of warm-up.

You may also see QuickJS-NG. It is a separate project that started as a copy of QuickJS. It is not a new name for the original. Both are easy to embed. Check each project's own feature list, because they release new versions at different times.

XS on microcontrollers

A microcontroller is a tiny computer on one chip. It runs things like a smart light, a thermostat, or a sensor. It often has very little memory.

XS is the JavaScript engine inside the Moddable SDK. An SDK is a set of tools for building programs for one kind of device. The Moddable documents say XS is made for devices with few resources (little memory and a slow chip). They describe:

  • Support for microcontroller chips.
  • JavaScript modules that are prepared before they reach the device.
  • Design choices for devices with few resources.
A message from a small devicePop out in the code editor (opens in a new tab)JavaScript
const reading = 23;const message = "sensor: " + reading; console.log(message);

Line 1 stores a sensor reading, 23. Line 2 builds a short message. Line 4 prints it, so the output is sensor: 23.

On a microcontroller, the whole product shares one fixed amount of memory. Your data, the screen, the network, the device's own code, and JavaScript all fit in it together. So a feature that is harmless on a laptop may not fit on the device at all.

This does not mean every XS program is tiny. It means the device needs careful choices about memory. The Moddable tools help with that: they can prepare modules ahead of time and build the JavaScript into the device's program.

When you compare engines for a device, ask these questions:

  • Which device features can JavaScript use?
  • How is the code prepared before it reaches the device?
  • How much memory does each part get?
  • How do you update the device later?

Which JavaScript features an engine supports is only one part of the choice.

There are other small engines too. Duktape is a small engine you can embed in C programs; it supports only some JavaScript features newer than ES5 (the 2009 version). Espruino is an interpreter for microcontrollers with a live console, where you type code and see the result at once. Each one has its own device features and language support, different from XS.

engine262 follows the language rules closely

The rules of JavaScript are written in one official document called ECMA-262. People also call it the specification, or spec for short.

engine262 is an engine that follows ECMA-262. It is itself written in JavaScript. Its project lists three goals:

  • Follow the spec exactly.
  • Let people look inside and see each step as it runs.
  • Be easy to change.

The project also says that speed is not a goal if it would hurt those three goals.

A tiny language rulePop out in the code editor (opens in a new tab)JavaScript
const price = 3;const count = 2; console.log(price + count);

Line 1 makes price. Line 2 makes count. Line 4 adds them, so it prints 5.

We call an engine like this a spec interpreter. It helps when the question is "what should this JavaScript mean?" A team can try a new rule in engine262 and watch it work. That is easier than changing a big, fast engine first. The engine262 project says it has also helped find mistakes in ECMA-262 and in test262 (the official set of JavaScript tests).

Real-life analogyA teacher reading the recipe

A teacher can cook a recipe word by word to check that the recipe itself makes sense. It is slow on purpose. engine262 does this for the language rules. It helps people check and try out the rules. It is not meant to serve a busy restaurant.

In real life: A teacher follows the recipe word by word
In JavaScript: engine262 follows each step of the spec
In real life: The teacher checks that the recipe makes sense
In JavaScript: The engine helps people check the language rules
In real life: The meal takes longer
In JavaScript: Speed is not the goal
In real life: The teacher can try a changed recipe
In JavaScript: A team can try a new rule

Use a spec interpreter to learn, test, and talk about what JavaScript code means. Use a big, fast engine when you ship a real app that must run well on real devices.

Two more names you may meet:

  • Boa is another engine you can embed. It is written in the Rust language and is tested against the spec. It is a separate project from engine262.
  • test262 is the official set of JavaScript tests. It is not an engine. How many tests an engine passes depends on which features you count and which engine version you test.

A tiny bytecode interpreter

You met bytecode in the Hermes section: a list of small, simple instructions that an engine runs one by one. Real engines use their own bytecode, which is private and much bigger. This lesson uses a made-up bytecode with only three instructions:

  • push puts a number on top of the stack. A stack is a pile of values where the newest value sits on top.
  • add takes the top two values off the stack and puts their sum back on top.
  • print reads the top value and prints it. The value stays on the stack.
Push, add, printPop out in the code editor (opens in a new tab)JavaScript
const program = [  { op: "push", value: 2 },  { op: "push", value: 3 },  { op: "add" },  { op: "print" },]; console.log(runBytecode(program).printed[0]);

Line 1 starts the list of instructions, called program. Lines 2 and 3 push 2 and then 3. Line 4 adds them. Line 5 prints the result. Line 8 runs the program with the lesson's interpreter, runBytecode, and logs the first printed value. So it prints 5.

The stack after each instruction. push 2 gives 2. push 3 puts 3 on top of 2. add takes both and leaves 5. print reads 5 and prints it.push 22[2]push 323[2, 3]add5[5]print5prints 5
Each instruction changes the stack, a pile where the newest value sits on top. add takes the top two values and puts back their sum. print reads the top value, 5.
Step through push, add, and print
Step 0 of 6Ready
Your turn: follow the blue line

Step through the lesson's tiny bytecode interpreter. It is a small model for learning, not a view inside a real engine.

Running in
  1. script
Next: line 1
  { op: "push", value: 2 },  { op: "push", value: 3 },  { op: "add" },  { op: "print" },]; console.log(runBytecode(program).printed[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 step-through above calls the same runBytecode function that the lesson's tests use. It is a small model for learning, not a view inside a real engine. Watch the stack: first 2, then 2 and 3, then 5. Then print reads 5.

Short bytecode like this suits small interpreters. They can run it without turning every repeated step into machine code. The cost is that they are usually slower than a good JIT after a long warm-up.

Choose for the device

To choose an engine, start with what the product needs most, not with an engine's name. The next small model picks from four simple kinds of engine. Its scores are made up. Never use them as real speed results.

Ask the chooser for a phone appJavaScript
const device = "phone";const result = chooseEngine(device); console.log(result.profile.label);

Line 1 picks the phone. Line 2 asks the model, with the chooseEngine function, which kind of engine fits. Line 4 prints the name of that kind. For a phone, this model prints Bytecode interpreter with AOT bytecode.

Four devices and what matters most for each. Phone app: startup. Microcontroller: memory. Server: peak speed. Spec testing: inspectable behavior.devicewhat matters mostPhone appstartupMicrocontrollermemoryServerpeak speedSpec testinginspectable behavior
Each of the 4 devices cares most about a different thing. So each one needs a different kind of engine.
Playground: choose an engine for one device
The chooser codeJavaScript
const device = "phone";const result = chooseEngine(device); console.log(result.profile.label);
Made-up resultBytecode interpreter with AOT bytecode
devicePhone app

The app should open quickly, without much extra work at the start.

best fitBytecode interpreter with AOT bytecode

The bytecode is made when the app is built, so the phone skips that work when the app opens.

JIT enginestart 4 / speed 10 / memory 8

It spends extra memory and warm-up work so code that repeats runs fast.

Bytecode interpreter with AOT bytecodestart 9 / speed 6 / memory 5

The bytecode is made when the app is built, so the phone skips that work when the app opens.

Small interpreterstart 8 / speed 4 / memory 9

A small engine leaves more of the device's little memory for the product.

Spec interpreterstart 3 / speed 1 / memory 3

It shows each step of the language rules clearly. Speed does not matter here.

Try it yourself

The model's answer. Phone app: The bytecode is made when the app is built, so the phone skips that work when the app opens.

The scores and the matching rule are made up for learning. They show what each device needs most. They are not real speed tests, and they are not advice on which engine to use.
Step through one phone choice
Step 0 of 4Ready
Your turn: follow the blue line

Step through the lesson's chooser. Its scores are made up, to show that a fast start, speed, and low memory pull in different directions.

Running in
  1. script
Next: line 1
const result = chooseEngine(device); console.log(result.profile.label);
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.

In the playground, change only one thing: the device. The reset button goes back to the phone app. Read the reason first, then the made-up scores. Each device needs something different:

  • A phone app needs a fast start.
  • A microcontroller needs to use little memory.
  • A server needs top speed after warm-up.
  • Spec testing needs to see each step clearly.

The table below lists real facts from each project's own documents. Read each row as one engine. It has no made-up scores.

What each engine's project says it is for (real facts, not model scores)
EngineWhere is it used?What does its project say it does?What does that mean for you?
HermesReact Native phone appsIt improves code ahead of time and stores it as compact bytecode.A fast app start is its main goal.
QuickJSInside other programsA bytecode interpreter that frees memory with reference counting and cycle removal.It aims to be small and easy to put inside another program.
XSThe Moddable SDK, for small devicesA JavaScript engine for devices with little memory and a slow chip.Its tools can prepare modules before they reach the device, to save memory.
engine262Work on the JavaScript rules (ECMA-262)An engine written in JavaScript that follows ECMA-262.It puts following the spec, showing each step, and being easy to change before speed.

Questions to ask before you choose

Most app teams rarely change their engine. Many other things often decide the engine before any speed test does:

  • the framework the team uses,
  • the device features JavaScript needs to reach,
  • how the app is kept safe,
  • the debugger,
  • links to code written in other languages, such as C,
  • and how the app is shipped to users.

So the useful skill is knowing which questions to ask:

  • Which moment matters most: the first start, code that runs over and over for hours, or a tiny amount of memory?
  • Which JavaScript features and device features does the product really need?
  • How will the team measure start time, memory, and how quickly the app responds, on the real device?
  • Does the job need a big engine for a real product, a small engine inside another program, or a spec interpreter for trying rules?

Measure on the device your users own. A result on a server does not tell you about a phone. A phone simulator on your laptop does not tell you about a microcontroller. A famous engine name does not tell you how much memory you have.

When you can, keep the choice easy to undo. Write a small test program that works like your product. Record how long it takes to start and how much memory it uses. Then run it on the real device. A clear need gives a useful comparison. A vague question like "which is fastest?" gives a vague answer.

What does each situation need most?
  • A phone app opens from bytecode made when the app was built.
  • A React Native app uses Hermes.
  • In this lesson's model, a device has only 256 KB of memory.
  • A program written in C needs a small JavaScript engine inside it.
  • A team wants to try a new language rule and watch it work.
  • A team checks a question about ECMA-262 or test262, the official tests.
Try it yourself
0 of 6 correct

Put each situation under the need that matters most for it. These cards help you practice thinking. They are not engine advice for every case.

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

Common misconceptions

  • "Small means old JavaScript." Small describes what the engine is built for. It says nothing about which version of JavaScript it runs.
  • "AOT bytecode is machine code for every chip." It is bytecode made early, for an interpreter to run. It makes no promise about every kind of chip.
  • "Reference counting can never handle cycles." Counting alone can't. That is why QuickJS adds a cycle removal step.
  • "A spec interpreter should win a speed test." engine262 says speed is not a goal if it would hurt its three goals.
  • "The chooser scores are facts." They are made-up scores for learning only.

The table below compares words that sound alike. Read each row as one word: what it means, and what it does not mean.

Words from this lesson that are easy to mix up
WordWhat does it mean?What does it not mean?
Small engineAn engine built for one main need, such as a fast start or low memoryAn engine that can't run modern JavaScript
AOT bytecodeBytecode made early, when the app is builtMachine code for every kind of chip
Reference countingCounting how many references point to each object (QuickJS adds a cycle removal step)A promise that every engine frees memory the same way
Spec interpreterA tool for checking and trying out what JavaScript code should doAn engine built to win speed tests for real apps

Practice exercises

Exercise 1 · Warm-upPredict a repeated total

What does this code print? Guess first, then run it.

Starter codePop out in the code editor (opens in a new tab)JavaScript
function addCartPrice(price, times) {
  let total = 0;
  for (let count = 0; count < times; count += 1) total += price;
  return total;
}

console.log(addCartPrice(3, 4));

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

    Exercise 2 · Warm-upRead a tiny stack

    What does this small stack program print?

    Starter codePop out in the code editor (opens in a new tab)JavaScript
    const stack = [];
    stack.push(2);
    stack.push(3);
    stack.push(stack.pop() + stack.pop());
    console.log(stack[0]);

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

      Exercise 3 · PracticeChoose for a phone

      Type the name of the kind of engine the model picks for a phone app.

      Starter codePop out in the code editor (opens in a new tab)JavaScript
      const phoneChoice = "Bytecode interpreter with AOT bytecode";
      console.log(phoneChoice);

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

        Exercise 4 · PracticeName the QuickJS memory method

        Which way of freeing memory do the QuickJS documents name?

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

          Exercise 5 · ChallengeChoose the first question

          A team is building the program for a small device. What should it ask first, before it chooses an engine?

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

            Exercise 6 · ChallengeTry out a new language rule

            A team that writes JavaScript's rules wants to try a new rule and watch it work. Which engine in this lesson fits that job?

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

              Check your understanding

              For each question, first ask what the job needs most. Keep the made-up model scores apart from the real facts about each project.

              Small engines quiz · 7 questionsScore: first tries count
              1. Question 1 of 7What does the warm-up function print?

                Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
                function addCartPrice(price, times) {
                  let total = 0;
                  for (let count = 0; count < times; count += 1) total += price;
                  return total;
                }
                console.log(addCartPrice(3, 4));

                Choose an answer to see the explanation.

              2. Question 2 of 7Why can Hermes help a React Native app start quickly?

                Choose an answer to see the explanation.

              3. Question 3 of 7Which sentence matches what the QuickJS documents say?

                Choose an answer to see the explanation.

              4. Question 4 of 7What does this small stack program print?

                Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
                const stack = [];
                stack.push(2);
                stack.push(3);
                stack.push(stack.pop() + stack.pop());
                console.log(stack[0]);

                Choose an answer to see the explanation.

              5. Question 5 of 7What is engine262 for?

                Choose an answer to see the explanation.

              6. Question 6 of 7In the made-up chooser, which device gets the small interpreter?

                Choose an answer to see the explanation.

              7. Question 7 of 7What should a team ask first when it chooses an engine?

                Choose an answer to see the explanation.

              Key takeaways

              • A small engine is built for one main need: a fast start, low memory use, fitting inside another program, or checking the language rules.
              • Hermes makes compact bytecode ahead of time, when the app is built, so React Native apps start fast.
              • QuickJS is small and easy to embed. It uses bytecode, and it frees memory with reference counting plus cycle removal.
              • XS is the engine in the Moddable SDK, made for small devices with little memory.
              • engine262 cares more about following the spec, showing each step, and being easy to change than about speed.
              • Choose from what the product needs most. Then measure on the real device.

              Remember the one-liner.
              A small engine is not simply slower or worse. It spends its small budget of start time, memory, and size on a different job.

              Coming next: JavaScript & WebAssembly. There, JavaScript works with WebAssembly, a compact kind of program file that runs the same way on many devices.

              CompleteFrontend Clear concepts. Working examples.