completefrontend
Open lab My progress
THE JAVASCRIPT FIELD GUIDE

Inside Deno, Bun & edge runtimes

Compare Deno, Bun, and edge runtimes through engines, host APIs, V8 isolates, common web APIs, and cold-start models.

By the end, you can
  • 01
    Tell the language from the runtimeName the engine and the extra tools that make Deno, Bun, Node, and edge platforms different.
  • 02
    Write handlers that run anywhereUse Request, Response, URL, fetch, and streams, and check that a tool exists before you use it.
  • 03
    Explain how code runs at the edgeExplain why each isolate has its own globals, why data there may not last, and what a cold start is.

One language, different runtimes

JavaScript is a language. On its own, it can't start a timer, read a file, or talk to the network. A runtime is the program that runs your JavaScript and gives it those extra tools.

Deno, Bun, and Node are three runtimes. Edge platforms, which run your code on servers close to your users, are runtimes too. They all run the same language. Each one makes its own choices about the extra tools.

Definition

A JavaScript engine is the part that reads and runs the language itself. A runtime is an engine plus extra tools: ways to reach files and the network, a way to load modules, and a way to schedule work for later. That is why the same JavaScript can act differently when it touches files, global names, or startup.

A card called app.js at the top. Arrows go from it to three boxes. The Deno box holds a V8 card, the Bun box holds a JavaScriptCore card, and the Node box holds a V8 card.app.jsV8DenoJavaScriptCoreBunV8Node
The same app.js can run in Deno, Bun, and Node. Each runtime has an engine inside: Deno and Node use V8, and Bun uses JavaScriptCore.

This lesson uses small request handlers. A handler is a function that gets one web request and gives back one answer. Handlers show the part every runtime shares. Then the lesson names what is different in each one:

  • Deno uses the V8 engine, plus code written in Rust.
  • Bun uses the JavaScriptCore engine, plus its own native code.
  • Many edge platforms run code in V8 isolates (explained below).

The previous Node.js architecture lesson looked inside one runtime built on V8. This lesson compares other runtimes with it. The Embedding V8 lesson explains isolates in more depth.

Start with web tools every runtime shares

A server handler often needs only two things: the web address that was asked for, and some text to send back. This example uses three tools that browsers also have:

  • Request holds one incoming request, including its address in request.url.
  • URL splits an address into parts, such as the path /cart.
  • Response holds the answer you send back.

Because the code uses only these shared tools, and no tool that belongs to one runtime, it runs in a browser and in Node without changes. Code like this is called portable.

A small handler that runs in many placesPop out in the code editor (opens in a new tab)JavaScript
async function handle(request) {  const url = new URL(request.url);  return new Response(`Hello from ${url.pathname}`);} (async () => {  const response = await handle(new Request("https://shop.example/cart"));  console.log(await response.text());})();

Line 1 names the handler handle. It gets one request. The word async means it gives back a promise, and line 7 waits for that promise with await. Line 2 makes a URL from request.url. Line 3 sends back a response. Its text includes the path. Line 7 makes a request for https://shop.example/cart and calls the handler. Line 8 reads the response text and prints Hello from /cart.

A tag that says Request, /cart goes into the handle function card. Out comes a Response card. An arrow goes from the Response down to a console window that shows Hello from /cart.Request/carthandle()ResponseconsoleHello from /cart
A Request for /cart goes in, and handle() gives back a Response. Reading the response body prints Hello from /cart.
Step through the handler
Step 0 of 5Ready
Your turn: follow the blue line

These steps replay the lesson's handler, one line at a time. The values were rebuilt from the example. They were not read from a running runtime.

Running in
  1. script
Next: line 7
async function handle(request) {  const url = new URL(request.url);  return new Response(`Hello from ${url.pathname}`);} (async () => {  console.log(await response.text());})();
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.

This does not mean every runtime has every browser tool. It means small pieces of code can use the shared tools first. Some code needs more, such as reading files, talking to a database, or using a tool only one platform offers. Put that code in a small separate function, called an adapter, so the rest of the handler stays portable.

Deno: V8, Rust, and Tokio

Deno's own documents describe three main parts:

  • V8 is the engine. It runs your JavaScript. Chrome and Node use it too.
  • Rust is the language Deno's extra tools are written in, such as reading files.
  • Tokio is a Rust library that keeps track of slow work, like network requests, and runs the next step when each one finishes.

Deno runs JavaScript, TypeScript, and WebAssembly. It is also careful by default. A permission is a yes or no you give when you start Deno. Your code needs permission before it can read files, use the network, or read environment variables. Loading the modules your code imports is the one thing that needs no permission. Give only what the code needs, such as one network address.

Permissions are a rule of the runtime, not of the language. Your handler is still ordinary JavaScript that uses the shared web tools.

Deno 2 can also run a lot of code written for Node:

  • Node's own modules, with imports that start with node:.
  • npm packages, with imports that start with npm:, or listed in package.json.

Not everything works. Some npm packages include compiled native code, called native addons. Those need a local node_modules folder and an extra permission. npm code also has to follow Deno's permissions.

Use a shared Response, not a Deno-only toolPop out in the code editor (opens in a new tab)JavaScript
const path = "/cart";console.log(new Response(`Saved ${path}`).status);

Line 1 stores a path. Line 2 makes a shared Response and prints its status code, 200. A status code is a number that says how the request went, and 200 means "OK". A new response gets 200 unless you choose another one. This code works in Deno, but it does not use Deno. That is a global, a name you can use anywhere without importing it, and only Deno has it. So this code works in other runtimes too.

The table compares the runtimes. Read each row as: the name, what it is built from, and what your code can see.

What each runtime is built from
NameWhat is it built from?What does your code see?
DenoThe V8 engine, with extra tools written in Rust and the Tokio library.The shared web tools, plus the Deno global and permissions.
BunThe JavaScriptCore engine, with its own native code.The shared web tools, many Node modules, and the Bun global.
Edge platformOften V8 isolates, but each platform is different.A small set of server tools, plus tools that only that platform has.
WinterTCNot a runtime. It is a standards group, also called Ecma TC55.A short list of web tools that every server runtime can offer.

Bun: JavaScriptCore and native code

Bun's documents say Bun uses JavaScriptCore as its engine. JavaScriptCore is Apple's engine, the one inside Safari. It is a different engine from V8. Both engines run the same JavaScript. Both runtimes also offer the shared web tools, such as fetch, Request, and Response.

You may read that Bun is written in Zig, another programming language. Bun was first known for that. Bun's current documents say its runtime is written in Rust. This lesson follows the current documents.

Bun also comes with two more tools: a bundler, which joins many files into one, and a test runner. Bun is still working on running all code written for Node. Before you move a Node app to Bun, check that each Node module and package your app uses works there.

An ordinary object works the same in every enginePop out in the code editor (opens in a new tab)JavaScript
const order = { item: "tea" };console.log(order.item);

Line 1 makes an ordinary object. Line 2 prints tea. V8 and JavaScriptCore are built differently on the inside, but this code means the same thing in both. Code that sticks to plain language features like this moves between runtimes easily.

Real-life analogyThe same recipe in different kitchens

You can cook one recipe in two kitchens. Each kitchen has a different stove and different tools. The food still comes out the same.

In real life: One recipe
In JavaScript: The JavaScript language
In real life: A V8 stove in one kitchen
In JavaScript: The engine Deno uses
In real life: A JavaScriptCore stove in another
In JavaScript: The engine Bun uses
In real life: The kitchen tools around the stove
In JavaScript: The runtime's extra tools

Find out which runtime you are in

Don't guess the runtime from the name of the operating system or from the browser's name. Sometimes you really do need a tool that only one runtime has. Then check that the exact tool you plan to use is there. Keep the code that uses it short.

The example below uses three tools you may not know yet:

  • typeof Deno gives the type of Deno as text. If no such name exists, it gives "undefined" instead of throwing an error.
  • a ? b : c means "if a is true, use b, otherwise use c".
  • ?. reads a property only if the thing before it exists. Otherwise it gives undefined and does not throw.
Check for each runtime in turnPop out in the code editor (opens in a new tab)JavaScript
const runtime =  typeof Deno !== "undefined" ? "deno" :  typeof Bun !== "undefined" ? "bun" :  globalThis.process?.versions?.node ? "node" :  "unknown"; console.log(runtime);

Lines 1 to 5 pick one name, checking in this order:

  1. Line 2: if a Deno global exists, the name is "deno".
  2. Line 3: if a Bun global exists, the name is "bun".
  3. Line 4: if process.versions.node exists, the name is "node". Node puts its version number there.
  4. Line 5: if none of these exist, the name is "unknown".

Line 7 prints the name. In Node, it prints node.

This name is handy in an error report. To use a feature, check for the feature itself instead. For example, don't call a Bun tool just because the name is bun. Check that the tool exists, write down what happens when it doesn't, and keep that check out of the portable handler. Then moving to another runtime later brings fewer surprises.

V8 isolates at the edge

An edge platform runs your code on many servers around the world, so each user reaches one close by. Cloudflare Workers is one example.

An isolate is a small, separate space inside V8 where one piece of code runs. It has its own memory and its own global names. Cloudflare's documents say:

  • One Workers runtime can run many isolates at once.
  • The memory of each isolate is kept apart from all other code in that runtime.

Keeping code apart like this is useful. Many apps, from many different people, can run side by side safely. But an isolate is not a safe place to keep data. Cloudflare also says:

  • The platform can shut down an isolate at any time.
  • Two requests may not reach the same isolate.

So a variable at the top of your module can vanish, or another request may never see it.

A small model: two stations, each with its own countPop out in the code editor (opens in a new tab)JavaScript
function createStation(name) {  return { name, globals: { orders: 0 } };} function serve(station) {  station.globals.orders += 1;  return station.name + ": " + station.globals.orders;} const first = createStation("first");const second = createStation("second");console.log(serve(first));console.log(serve(second));

This code is a model. It is not how V8 works inside. Each "station" stands for one isolate.

  1. Line 1 makes a station. Each station gets its own globals object with an orders count of 0.
  2. Line 5 starts serve. Line 6 adds 1 to the count of that one station only.
  3. Lines 10 and 11 make two stations, first and second.
  4. Lines 12 and 13 serve each station once. They print first: 1, then second: 1.

Serving first did not change the count in second. Each station keeps its own globals, just like each isolate does.

A window called one runtime holds two boxes with a dashed line between them. The first box holds a card that says orders: 1, and so does the second box. Under them the output is first: 1 and second: 1.one runtimeorders: 1firstorders: 1secondfirst: 1second: 1
One runtime holds two separate stations, like two isolates. Each has its own orders count, so serving each one once prints first: 1 and second: 1.

The Embedding V8 lesson explains isolates inside the engine. Here the rule is simple: pass data in with each request, or keep it in a storage service. Don't count on one isolate staying alive.

Edge platforms also set limits. Cloudflare Workers counts CPU time, the time your code is actually running, apart from time spent waiting for the network. How much CPU time you get depends on your plan and settings.

Node's modules are not promised on every edge platform. On Cloudflare, each project sets a compatibility date. Projects with a date of 2026-08-04 or later get Node support turned on by default. Projects with an older date can turn it on with the nodejs_compat setting. Some Node modules are only partly there, or can be imported but do nothing. Check the exact module and your project's date before you rely on it.

Step through the handler again

Step through the handler from earlier once more. It follows one request from its address to its response. The steps replay the lesson's own handler code. They do not pause Deno, Bun, Node, or an edge platform. The values you see were rebuilt from the example, not read from a running runtime.

Step through the handler
Step 0 of 5Ready
Your turn: follow the blue line

These steps replay the lesson's handler, one line at a time. The values were rebuilt from the example. They were not read from a running runtime.

Running in
  1. script
Next: line 7
async function handle(request) {  const url = new URL(request.url);  return new Response(`Hello from ${url.pathname}`);} (async () => {  console.log(await response.text());})();
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.

Notice what the steps do not show:

  • the engine's memory,
  • how the platform decides which isolate gets the request,
  • whether the next request will reach the same isolate.

Those belong to the runtime and the platform. The part your code controls stays the same everywhere: a request goes in, and a response comes out.

Keep shared data in a storage service

Sometimes an order must still be there for the next request, or for another isolate. Keep it in a database, a cache service, a queue, or a storage tool the platform offers. Data kept there lasts. Data kept in an isolate's globals may not.

WinterTC and the shared web tools

WinterTC is a group that writes standards. Its official name is Ecma TC55. It works on server runtimes that use the same tools as the web. Its goal is a short, agreed list of web tools that every server runtime can offer. This list is called the minimum common API. An API is a set of tools your code can call.

In everyday code, the shared tools are ones you may know from the browser:

  • fetch, to send a request over the network,
  • Request, Response, and URL,
  • TextEncoder, to turn text into bytes,
  • crypto.randomUUID, to make a random id,
  • timers, such as setTimeout,
  • streams, to send or read data in small pieces.

Each runtime still decides its own versions, limits, and extra tools.

Check the type of six shared toolsPop out in the code editor (opens in a new tab)JavaScript
const commonApis = [  ["fetch", typeof fetch],  ["Request", typeof Request],  ["Response", typeof Response],  ["URL", typeof URL],  ["TextEncoder", typeof TextEncoder],  ["crypto.randomUUID", typeof crypto.randomUUID],]; console.log(commonApis.map(([name, type]) => name + ": " + type).join(", "));

Lines 1 to 8 make a list. Each item is a pair: a tool's name and its type from typeof. Line 10 joins the pairs into one line of text. When all six tools exist, it prints fetch: function, Request: function, Response: function, URL: function, TextEncoder: function, crypto.randomUUID: function.

This only checks that the tools exist. It does not promise that every runtime has the same limits or the same extra tools.

Name any shared tool that is missingPop out in the code editor (opens in a new tab)JavaScript
const commonApis = {  fetch,  Request,  Response,  URL,  TextEncoder,  "crypto.randomUUID": crypto.randomUUID,}; const missing = Object.entries(commonApis)  .filter(([, value]) => typeof value !== "function")  .map(([name]) => name); console.log(missing.length === 0 ? "portable base available" : "missing: " + missing.join(", "));
  1. Lines 1 to 8 collect the tools a handler wants, by name.
  2. Lines 10 to 12 keep only the names whose tool is not a function. Those are the missing ones.
  3. Line 14 prints portable base available when nothing is missing. Otherwise it prints missing: and the missing names.

In a real adapter, use this result to pick a backup plan. Or write down that the app needs a runtime with these tools.

Language, shared web tool, or runtime extra?
  • Promise and async functions
  • Request passed to a handler
  • new Response("ok")
  • The Deno global
  • The Bun global
  • TextEncoder
Try it yourself
0 of 6 correct

Put each card where it comes from: the JavaScript language, the shared web tools, or one runtime or platform.

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

Cold starts and warm starts

Before new code can answer its first request, the platform has to get a fresh place ready to run it. That setup work is called a cold start. When the platform can reuse a place that is already ready, that is a warm start, and it skips the setup.

How long a cold start takes depends on many things: the platform, your code, the engine, what is already cached, and the platform's own rules. So don't take a number from one test and treat it as true everywhere.

Edge platforms often start a new isolate instead of a whole new container, which is a full, separate copy of a system for your app. Cloudflare says isolates are small and are made inside a runtime that is already running. That is why they can be quick to start. It does not promise one start time for every edge platform or every app.

A made-up model that counts cold startsPop out in the code editor (opens in a new tab)JavaScript
function countColdStarts(requestTimes, warmForMs) {  let lastRequest = null;  let coldStarts = 0;  for (const time of requestTimes) {    if (lastRequest === null || time - lastRequest > warmForMs) coldStarts += 1;    lastRequest = time;  }  return coldStarts;} console.log(countColdStarts([0, 5, 10, 50], 20));

The model uses made-up numbers. A request counts as warm if it comes within a short time of the last one. This time is called the warm window. Here it is 20.

  1. Line 1 gets a list of request times and the warm window, warmForMs.
  2. Line 5 counts a cold start when there is no earlier request, or when the gap since the last request is more than the warm window.
  3. Line 6 remembers this request's time for the next turn.
  4. Line 11 runs the model with times 0, 5, 10, and 50. It prints 2.

The 2 shows how the rule works. It is not a timing from Deno, Bun, or any edge platform.

Four request time cards: 0, 5, 10, and 50, with a warm window of 20. The gaps between them are 5, 5, and 40. The first and last requests are cold; the middle two are warm.warm window: 200gap 55gap 510gap 4050coldwarmwarmcold
Four requests arrive at times 0, 5, 10, and 50. The first one and the one after the gap of 40 are cold, so the model counts 2.
Step through the made-up cold-start model
Step 0 of 7Ready
Your turn: follow the blue line

These steps follow a small, made-up model of cold and warm starts. A real platform makes its own choices about when to reuse an isolate and when to shut it down.

Running in
  1. script
Next: line 11
function countColdStarts(requestTimes, warmForMs) {  let lastRequest = null;  let coldStarts = 0;  for (const time of requestTimes) {    if (lastRequest === null || time - lastRequest > warmForMs) coldStarts += 1;    lastRequest = time;  }  return coldStarts;} 
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: change the gap between requests
The cold-start modelJavaScript
function countColdStarts(requestTimes, warmForMs) {  let lastRequest = null;  let coldStarts = 0;  for (const time of requestTimes) {    if (lastRequest === null || time - lastRequest > warmForMs) coldStarts += 1;    lastRequest = time;  }  return coldStarts;} console.log(countColdStarts([0, 5, 10, 50], 20));
What the model counts1 cold
request times0, 15, 30, 45

Four made-up request times. Each one comes one gap after the last.

warm window20

A gap of more than 20 counts as cold.

cold starts1

This count comes from the model. It is not a timing from a real platform.

Try it yourself

With a gap of 15 between requests, the model counts 1 cold start. The first request is always cold. Each later one is cold only if its gap is more than 20.

This playground only counts starts in a made-up model. It does not time Deno, Bun, Node, or any edge platform.
Real-life analogyFood trucks near customers

A food truck parks near its customers. Inside are several small cooking stations. Lighting a cold stove takes time. A station that is already hot can start the next order right away.

In real life: Small cooking stations in one truck
In JavaScript: Many isolates in one runtime
In real life: A station cooks one nearby order
In JavaScript: One request runs in one isolate
In real life: A cold stove needs lighting first
In JavaScript: A cold start sets up a fresh isolate
In real life: A hot station cooks the next order
In JavaScript: A warm start reuses an isolate

Pick tools that work everywhere

When you start a server feature, begin with a plain handler if it fits. It gets a request, reads the address with URL, maybe calls another service with fetch, and sends back a response. You can test most of this code without tying the whole app to one runtime.

Then make the runtime-only code easy to spot. Each of these is fine to use when you need it:

  • a Deno permission,
  • a tool from the Bun global,
  • a Node module,
  • a tool that only one edge platform offers.

Put each one in its own named adapter. Next to it, write down the backup plan, or which runtime the app needs.

  • For shared server code, use Request, Response, URL, fetch, streams, and TextEncoder.
  • Before you call an optional tool, check that it exists.
  • Keep data that must last outside isolate globals.
  • Measure cold starts on the platform you really use. Don't rely on numbers from somewhere else.

Common mistakes about runtimes

Short phrases like "the V8 runtime" or "the edge is fast" are easy to say, but they skip details. The engine, the runtime, and the platform your app is put on each control different parts of what you see.

  • "JavaScript needs V8." No. Bun runs JavaScript on JavaScriptCore.
  • "An isolate is a safe place to keep data." No. The platform can reuse an isolate or shut it down. Data that must last belongs in a storage service.
  • "WinterTC removes all differences between runtimes." No. It aims for a shared list of tools. Each runtime still keeps its own extra tools and rules.
  • "A cold start takes one fixed amount of time." No. It depends on the platform and the app, so measure the one you use.

The table puts four words side by side. Read each row as: the word, what it means, and what people often think it means.

Four words that are easy to mix up
WordWhat does it mean?What does it not mean?
IsolateA small, separate space inside V8 with its own memory.A new program started by the operating system for each request.
Global dataThe platform may reuse it, or throw it away.A database, or a safe place that every request can see.
Cold startThe setup needed before a fresh place can run your code.One fixed amount of time that is the same everywhere.
Warm startThe platform may reuse a place that is already set up.A promise that every later request sees the same globals.

Practice exercises

Exercise 1 · Warm-upPredict what the handler prints

The handler sends back the path of the address. What does it print for the cart address?

Starter codePop out in the code editor (opens in a new tab)JavaScript
async function handle(request) {
  return new Response(new URL(request.url).pathname);
}

(async () => {
  const response = await handle(new Request("https://shop.example/cart"));
  console.log(await response.text());
})();

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

    Exercise 2 · Warm-upName the runtime in Node

    What name does this code print when it runs in Node?

    Starter codePop out in the code editor (opens in a new tab)JavaScript
    const runtime = globalThis.process?.versions?.node ? "node" : "unknown";
    console.log(runtime);

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

      Exercise 3 · PracticePick the shared tools for a handler

      You want one handler to run in several runtimes. Which two shared tools should it get and send back?

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

        Exercise 4 · PracticeKeep a real order safe

        A checkout order must still be there when the next request reaches a different isolate. Where should you keep it?

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

          Exercise 5 · ChallengeCount the cold starts

          The requests come at times 0, 5, 10, and 50, and the warm window is 20. How many cold starts does the model count?

          Starter codePop out in the code editor (opens in a new tab)JavaScript
          console.log(countColdStarts([0, 5, 10, 50], 20));

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

            Exercise 6 · ChallengeUse a runtime-only tool in a real app

            Your app needs one tool that only one runtime has. What should you check for before you use it?

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

              Check your understanding

              For each question, first ask which part it is about: the language, the engine, the runtime, the platform, or the made-up cold-start model.

              Deno, Bun, and edge runtimes quiz · 7 questionsScore: first tries count
              1. Question 1 of 7What does this handler print for https://shop.example/cart?

                Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
                async function handle(request) {
                  return new Response(new URL(request.url).pathname);
                }
                (async () => {
                  const response = await handle(new Request("https://shop.example/cart"));
                  console.log(await response.text());
                })();

                Choose an answer to see the explanation.

              2. Question 2 of 7Which sentence tells an engine and a runtime apart correctly?

                Choose an answer to see the explanation.

              3. Question 3 of 7What name does the lesson's runtime check give in Node, where process.versions.node exists?

                Choose an answer to see the explanation.

              4. Question 4 of 7What does Cloudflare say about V8 isolates in Workers?

                Choose an answer to see the explanation.

              5. Question 5 of 7What is WinterTC, or Ecma TC55, working on?

                Choose an answer to see the explanation.

              6. Question 6 of 7In the cold-start model, warmForMs is 20. Why is the request at 50 cold when the one before it came at 10?

                Choose an answer to see the explanation.

              7. Question 7 of 7An edge handler takes an order that must still be there for later requests, even if its isolate is shut down. Where should it keep the order?

                Choose an answer to see the explanation.

              Key takeaways

              • JavaScript is the language. An engine runs it. A runtime adds extra tools, such as files, network, and timers.
              • Deno uses the V8 engine, with Rust and Tokio around it. Bun uses the JavaScriptCore engine, with its own native code.
              • Request, Response, URL, fetch, streams, and TextEncoder are a good shared base for server code.
              • Each edge isolate has its own memory and globals. The platform can reuse or shut down an isolate, so globals are not a safe place to keep data.
              • WinterTC, also called Ecma TC55, works on a short list of web tools that every server runtime can offer.
              • Measure cold starts on the platform you use. The lesson's model only shows how warm starts are counted.

              Remember the one-liner.
              Use the shared web tools first, and keep each runtime-only tool in its own small adapter.

              Coming next: Small & embedded engines. These engines run on small devices. They use less memory and start faster, but some leave out parts of the language.

              CompleteFrontend Clear concepts. Working examples.