cf.completefrontendCode editorOpen lab
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 constraintExplain why phone apps, microcontrollers, servers, and specification work ask for different engine designs.
  • 02
    Explain prepared bytecodeDescribe how Hermes can prepare compact bytecode at build time, and why that helps React Native startup.
  • 03
    Separate speed from purposeCompare QuickJS, XS, and engine262 without treating one engine as best for every job.

Why make a small engine?

A JavaScript engine turns source code into behavior. A browser engine serves a huge web platform, but some jobs need a smaller tool: a phone app wants a quick start, a tiny device has little memory, and a language team wants behavior that is easy to inspect.

Definition

A small engine is a JavaScript engine designed around a narrow constraint such as startup, memory, embedding, or checking the specification. It is not a lesser version of JavaScript; it makes different trade-offs.

There is no universal winner. A server can wait for repeated code to become fast. A device with a small memory budget cannot. A specification experiment needs a clear explanation of each rule more than a fast loop.

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 creates addCartPrice. Line 2 starts total at zero. Line 3 adds the price once for each item. Line 7 calls the function with price 3 and four items, so it prints 12.

An interpreter-only engine can run this same function steadily every time. A just-in-time, or JIT, engine may spend warm-up work learning that the loop repeats and later run it faster. That does not make either engine better without knowing the product's constraint.

Real-life analogyA camping stove

A full restaurant kitchen can prepare many dishes at once. A camping stove is light, starts quickly, and fits in a backpack; it makes a simpler meal more slowly, and that is exactly the point.

In real life: A camping stove fits in a backpack
In JavaScript: A small engine fits a constrained product
In real life: It starts with little setup
In JavaScript: Startup work stays small
In real life: It cooks a simple meal
In JavaScript: It focuses on a narrow job
In real life: A full kitchen handles more at once
In JavaScript: A large JIT can optimize longer workloads

Where the analogy stops: Engine size is not literally a stove size. Real performance depends on code, device, host APIs, and memory limits.

This lesson follows Inside Deno, Bun & edge runtimes. It looks below a runtime and asks what each engine deliberately leaves in or out.

A steady interpreter and a warming JIT

An interpreter reads a representation of a program and performs its instructions. A JIT compiler translates frequently used code while the program runs, aiming for more peak speed after it has observed real behavior.

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 creates a tiny function. Line 2 adds one to price. Line 4 calls it with 3, so the visible output is 4.

An interpreter can execute those operations directly on every call. A JIT can first interpret or compile a general version, collect facts during many calls, then compile a specialized version. The gain is for repeated work; the cost is startup work, generated code, and bookkeeping.

That distinction explains why startup and peak speed are separate columns. Opening an app once is different from processing a busy server route all day. Always ask which moment a user feels.

Made-up scores for a teaching model, not benchmarks
Engine modelStartup scorePeak speed scoreMemory scoreTypical fit
JIT engine4108Server
AOT bytecode interpreter965Phone app
Small interpreter849256 KB microcontroller
Spec interpreter313Spec testing

The scores above are deliberately made up. A larger number means better for that one column in this model. They give us a simple language for choosing, not a claim about actual milliseconds or megabytes.

Hermes and prepared bytecode

Hermes is a JavaScript engine optimized for fast startup of React Native apps. Its official project describes ahead-of-time static optimization and compact bytecode. The useful idea is that some preparation can happen while an app is built instead of when a user opens it.

A prepared app artifactPop 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 a teaching label, not real Hermes bytecode. Line 3 creates openApp. Line 4 logs the prepared label. Line 7 calls the function, so the code prints prepared at build time.

Real-life analogyPre-chopped vegetables

Pre-chopped vegetables let cooking begin sooner because the cutting was done before dinner. Hermes's prepared bytecode has the same one idea: parsing and compiling to bytecode can happen at build time, before the app is installed.

In real life: Vegetables are chopped before cooking
In JavaScript: App source is prepared at build time
In real life: Cooking starts sooner
In JavaScript: The app avoids some startup preparation
In real life: The cook still follows steps
In JavaScript: The engine still executes bytecode
In real life: The recipe was chosen earlier
In JavaScript: Build tools choose the artifact

Where the analogy stops: Bytecode is not food, and preparation does not remove every startup cost such as loading the app and initializing native code.

Do not turn that into “Hermes never compiles.” The documented claim is about ahead-of-time static optimization and compact bytecode. Exact behavior depends on the Hermes version and the React Native integration, so use product tools for a real app measurement.

For a reader, the design lesson is simple: moving work from startup to build time can improve the first moment of an app. It also makes the shipped artifact part of the runtime plan.

QuickJS: small and embeddable

QuickJS describes itself as a small, embeddable JavaScript engine with a fast interpreter and very low startup time. It compiles JavaScript sources to bytecode. Its memory management uses reference counting with cycle removal.

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 creates one order object. Line 2 gives that same object a second name. Line 4 reads through anotherName, so the result is tea.

Reference counting means a runtime can track how many references point to an object. When ordinary references go away, a count can fall. A cycle needs extra work because two objects may keep each other's counts above zero even when nothing else can reach them.

QuickJS documents cycle removal alongside reference counting. That phrase matters: reference counting alone has a known cycle problem, and a production implementation needs a plan for it. Continue with Reference counting & cycles for the algorithm in small steps.

Embedding means a larger native program can host JavaScript for a focused feature. The engine's small footprint and direct integration story can matter more than maximizing a hot loop after minutes of warm-up.

XS on microcontrollers

XS is the JavaScript engine in the Moddable SDK. Moddable documents XS for resource-constrained targets and documents support around microcontroller platforms, precompiled JavaScript modules, and design considerations for constrained targets.

A small device readingPop 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. Line 2 builds a short message. Line 4 prints it, so the output is sensor: 23.

A microcontroller often has a fixed memory budget for the whole product: your data, display, network buffers, native code, and JavaScript all share it. That changes good engineering choices. A feature that is harmless in a large desktop process may not fit on a device with a very small RAM limit.

The important claim is not that every XS program is tiny. It is that the host environment values predictable, resource-aware choices. Moddable's documentation includes precompiled modules and tools for linking JavaScript into embedded products, which fits that goal.

When comparing an embedded engine, ask which host APIs exist, how code is prepared, how memory is budgeted, and how the device is updated. Syntax support is only one part of the decision.

engine262 reads the spec closely

engine262 is an implementation of ECMA-262 written in JavaScript. Its project states three goals: specification compliance, introspection, and ease of modification. It also names speed at the expense of those goals as a non-goal.

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 creates price. Line 2 creates count. Line 4 adds them, so it prints 5.

A spec interpreter is useful when the question is “what should JavaScript mean?” It can make a proposed rule easier to prototype and inspect than changing an optimizing production engine first. engine262 says it has also been used to find bugs in ECMA-262 and test262.

Real-life analogyA teacher reading the recipe

A teacher can follow a recipe word by word to check whether the recipe itself makes sense. It is slow on purpose. engine262 plays that role for language rules: it helps people check and explore the book, not serve a busy restaurant.

In real life: A teacher follows the recipe word by word
In JavaScript: engine262 follows specification operations closely
In real life: The teacher checks the recipe
In JavaScript: The engine helps inspect language rules
In real life: The meal takes longer
In JavaScript: Speed is not the target
In real life: A changed recipe can be tried
In JavaScript: A proposal can be explored

Where the analogy stops: A specification is more detailed than a recipe, and engine262 is still software with its own implementation choices.

Use a spec interpreter to understand, test, and discuss semantics. Use a production engine when your job is delivering an app under real performance and platform constraints.

A tiny bytecode interpreter

Bytecode is a compact instruction format that an engine can interpret. Production bytecode formats are private and much more complex. This model has only three operations: push a number, add two values, and print a value.

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]);

Lines 2 and 3 put 2 and 3 into the program. Line 4 asks for addition. Line 5 asks for printing. Line 8 runs the lesson's interpreter and logs the first printed value, so it prints 5.

Step through push, add, and print
Step 0 of 6Ready
Your turn: follow the blue line

Step through an instrumented replay of the lesson's tiny bytecode interpreter. It is a teaching model, not a view into a production engine.

Running in
  1. script
Next: line 1
Click the blue line to take the next stepPop out in the code editor (opens in a new tab)JavaScript
  { op: "push", value: 2 },  { op: "push", value: 3 },  { op: "add" },  { op: "print" },]; console.log(runBytecode(program).printed[0]);
CallStoreChangeResultRun = next line. 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 uses the same runBytecode function as the tests. It is an instrumented teaching model, not an engine debugger. Watch how the stack becomes 2, then 2 and 3, then 5, before print reads 5.

Small interpreters can benefit from such a compact representation without needing to turn every repeated operation into native machine code. The trade-off is usually less peak speed than a capable JIT after long warm-up.

Choose for the device

Engine selection starts with a constraint, not a brand name. The next model chooses among four deliberately simplified profiles. Its scores are made up and must never be used as benchmark evidence.

Ask the chooserJavaScript
const device = "phone";const result = chooseEngine(device); console.log(result.profile.label);

Line 1 names a phone. Line 2 asks the model for a fit. Line 4 logs the chosen label. In this model it would print Bytecode interpreter with AOT bytecode.

Playground: choose an engine for one device
The chooser modelJavaScript
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 carrying a large runtime cost.

fitsBytecode interpreter with AOT bytecode

Build-time bytecode avoids parsing the app when it opens.

JIT enginestartup 4 / speed 10 / memory 8

It can spend memory and warm-up work to make repeated code fast.

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

Build-time bytecode avoids parsing the app when it opens.

Small interpreterstartup 8 / speed 4 / memory 9

A small runtime leaves scarce device memory for the product.

Spec interpreterstartup 3 / speed 1 / memory 3

It favors readable specification behavior over application speed.

Try it yourself

Made-up model result: Phone app: Build-time bytecode avoids parsing the app when it opens.

The scores and matching rule are made up for teaching. They compare constraints; they are not benchmark data or a procurement recommendation.
Step through one phone choice
Step 0 of 4Ready
Your turn: follow the blue line

Replay the actual lesson chooser. Its scores are deliberately made up to show competing startup, speed, and memory goals.

Running in
  1. script
Next: line 1
Click the blue line to take the next stepPop out in the code editor (opens in a new tab)JavaScript
const result = chooseEngine(device); console.log(result.profile.label);
CallStoreChangeResultRun = next line. 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.

Change exactly one input: the device. The reset button restores the phone app. Compare the reason first, then the made-up scores. A server asks for peak speed; spec testing asks for inspectable behavior; the microcontroller asks for memory discipline.

Official project focus, kept separate from model scores
EngineTypical contextDocumented focusPractical reading
HermesReact NativeAhead-of-time static optimization and compact bytecodeFast application startup is a stated focus.
QuickJSEmbedded programsBytecode interpreter with reference counting and cycle removalSmall and embeddable are its stated goals.
XSModdable embedded SDKJavaScript engine for resource-constrained targetsThe docs include precompiled modules and resource-constrained design choices.
engine262ECMA-262 researchJavaScript implementation of ECMA-262It explicitly puts compliance, introspection, and modification before speed.

Practical selection questions

Most application teams do not swap engines casually. Their framework, host APIs, security model, debugger, native bindings, and deployment workflow may decide the choice before a microbenchmark does. The useful skill is knowing which question to ask.

  • Is the important moment cold startup, a long-running hot path, or a tiny memory budget?
  • Which JavaScript features and host APIs does the product actually need?
  • How will the team measure startup, memory, and responsiveness on target hardware?
  • Does the task need a production runtime, an embeddable scripting engine, or a specification experiment?

Measure the product on the device that users own. A server result does not prove a phone result. A desktop simulator does not prove a microcontroller result. A familiar engine name does not replace a memory budget.

Keep the decision reversible when you can. Write a small workload that resembles the product, record the startup path and memory use, then test it on the actual target. A clear constraint makes a comparison useful; a vague speed question produces a vague answer.

Which constraint comes first?
  • A phone app opens from prepared bytecode.
  • A React Native app uses Hermes.
  • A device has only 256 KB RAM in this lesson model.
  • A native tool needs an embeddable JavaScript engine.
  • A language proposal needs a behavior playground.
  • A team investigates an ECMA-262 or test262 question.
Try it yourself
0 of 6 correct

Sort each situation by the main constraint it highlights. The cards are prompts for reasoning, not universal engine recommendations.

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 is a design constraint, not a language-era label.
  • “AOT bytecode is universal native code.” It is prepared bytecode for an interpreter, not a promise about every CPU.
  • “Reference counting cannot handle cycles.” Counting alone cannot; QuickJS documents cycle removal too.
  • “A spec interpreter should win a speed test.” engine262 says speed at the expense of its stated goals is a non-goal.
  • “The chooser scores are facts.” They are made-up teaching scores only.
Terms that sound similar but solve different problems
TermWhat it meansNot this
Small engineAn engine designed around constrained goalsAn engine that cannot run modern JavaScript.
AOT bytecodeCode prepared before the app is installedNative machine code for every device.
Reference countingCounted ownership with cycle handling in QuickJSA promise that every engine frees memory the same way.
Spec interpreterA tool for checking and exploring language behaviorA benchmark target for production app speed.

Practice exercises

Exercise 1 · Warm-upPredict a repeated total

Predict the output before running the code.

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 little 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 profile label the phone model should use.

      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 QuickJS memory work

        Which memory technique does the QuickJS documentation name?

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

          Exercise 5 · ChallengeChoose a first product question

          A team is building a small device controller. What should it ask before choosing an engine?

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

            Exercise 6 · ChallengeApply it to a language proposal

            A language team wants to inspect a proposed JavaScript rule. Which engine in this lesson fits that job?

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

              Check your understanding

              Use the job, then the constraint. Keep the model scores separate from the official 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 statement matches QuickJS documentation?

                Choose an answer to see the explanation.

              4. Question 4 of 7What does this tiny bytecode model 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 scores, which target favors the small interpreter?

                Choose an answer to see the explanation.

              7. Question 7 of 7What is the useful first selection question?

                Choose an answer to see the explanation.

              Key takeaways

              • Small engines choose a narrow goal such as startup, memory, embedding, or specification work.
              • Hermes prepares compact bytecode with ahead-of-time static optimization for React Native startup.
              • QuickJS is small and embeddable, uses bytecode, and documents reference counting with cycle removal.
              • XS is part of the Moddable SDK for resource-constrained embedded targets.
              • engine262 prioritizes specification compliance, introspection, and modification over speed.
              • Choose from the product constraint, then measure on the target device.

              Remember the one-liner.
              A small engine is not universally slower or worse; it is built to spend its limited startup, memory, and complexity budget on a different job.

              Coming next: JavaScript & WebAssembly, where JavaScript calls a compact, portable binary format.

              CompleteFrontend Clear concepts. Working examples.