completefrontend
Open lab My progress
THE JAVASCRIPT FIELD GUIDE

Writing engine-friendly code

Learn practical JavaScript habits for stable shapes, packed arrays, predictable call sites, and measured hot-path optimization.

By the end, you can
  • 01
    Build objects the same wayGive objects of one kind the same properties, in the same order, and explain why that helps hot code.
  • 02
    Spot variety at one lineTell apart the two notes the engine keeps: which function a call runs, and which object shape a property read sees.
  • 03
    Measure before changing codeRecord a profile of a real slow action before you use any of these habits in real code.

Measure before changing code

This lesson is about small habits that help the JavaScript engine run your code fast. The engine is the part of the browser (or of Node) that runs your JavaScript.

These habits only matter in hot code. Hot code is code that runs thousands of times, in a spot you have measured as slow. Everywhere else, write the clearest code you can.

Definition

Engine-friendly code is clear JavaScript that does the same repeated work in the same way each time. When the work looks the same each time, the engine can prepare a faster path for it.

Engines already make ordinary code fast. You don't need to guess what the engine wants in every file. Before you add any habit from this lesson, check three things:

  • A profile points at the slow spot. A profile is a recording, made with the browser's performance tools, of where the time goes.
  • A user can feel the slow part, such as a checkout button that takes a long time.
  • A quick test before and after your change shows that it helped.

This lesson puts ideas from earlier lessons to work: hidden classes, inline caches, elements kinds, type feedback, and speculation and deoptimization.

Build similar objects the same way

Inside the engine, each object has a shape. A shape is a short note the engine keeps about an object. It lists which property names the object has, and in what order they were added. Objects built the same way share one shape.

So here is a simple habit for hot code: give every object of one kind the same properties, in the same order.

A class with a constructor makes this easy. The constructor is the function that runs each time you write new Order(...). It sets up every new order in the same steps.

Two orders built the same wayPop out in the code editor (opens in a new tab)JavaScript
class Order {  constructor(item, qty) {    this.item = item;    this.qty = qty;  }} const first = new Order("tea", 3);const second = new Order("coffee", 2);console.log(Object.keys(first).join(","));console.log(Object.keys(second).join(","));

Line by line:

  • Line 1 starts the Order class.
  • Lines 2 to 4 are the constructor. It writes item first, then qty (short for quantity).
  • Lines 8 and 9 make two orders: one for tea and one for coffee.
  • Lines 10 and 11 print the property names of each order. Object.keys gives the names in the order they were added, and .join(",") puts commas between them. Both lines print item,qty.
Two carts made by new Order. The first cart, from new Order tea 3, holds the names item then qty. The second cart, from new Order coffee 2, also holds item then qty. Both print item,qty.new Order("tea", 3)itemqtyfirstitem,qtysame ordernew Order("coffee", 2)itemqtyseconditem,qty
Each cart is one order, and the cards are its property names, left to right. The values differ, but both orders get item first and qty second.

The values are different, but the names arrive in the same order. So both orders get the same shape.

Real-life analogyKitchen drawers

Think of a kitchen where the spoons always go in the first drawer. You reach for a spoon without looking. Now picture a home where each person puts the spoons in a different drawer. Each time, you have to open drawers and search first.

In real life: Spoons always go in the first drawer
In JavaScript: Every order gets item first, then qty
In real life: You grab a spoon without looking
In JavaScript: The engine already knows where qty is
In real life: Spoons in different drawers mean a search
In JavaScript: Objects built in different orders need extra checks
Step through: two orders built by one class
Step 0 of 8Ready
Your turn: follow the blue line

A replay of real calls to the Order class. It shows how the orders are built. It does not look inside the engine.

Running in
  1. script
Next: line 1
  constructor(item, qty) {    this.item = item;    this.qty = qty;  }} const first = new Order("tea", 3);const second = new Order("coffee", 2);console.log(Object.keys(first).join(","));console.log(Object.keys(second).join(","));
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.

Give every field a value at the start

Some fields are optional. For example, an order may or may not have a note. Optional does not have to mean "added later". In the constructor, you can give the field the value undefined right away. Then every order has a note, even when it is empty.

Here is what happens when two objects get their fields later, in different orders:

Fields added later, in two different ordersPop out in the code editor (opens in a new tab)JavaScript
const a = {};a.item = "tea";a.qty = 3; const b = {};b.qty = 3;b.item = "tea"; console.log(Object.keys(a).join(","));console.log(Object.keys(b).join(","));
  • Lines 1 to 3 make a, then add item, then qty.
  • Lines 5 to 7 make b, then add the same two names the other way round.
  • Line 9 prints item,qty. Line 10 prints qty,item.

The two objects have the same names, but not in the same order. So they do not share a shape. One constructor stops this from happening by accident, because it always writes the fields in one order.

Don't write a constructor for an object you make only once. Use one when many objects of one kind go through the same repeated work. The goal is one simple rule for building them. You don't need classes everywhere.

Avoid delete and array holes

The delete keyword removes a property from an object. In hot code this can slow things down. The object changes after the engine has already learned its shape.

If a field is optional, set it to undefined instead. The property stays. Only its value is empty.

delete, undefined, and an array with a holePop out in the code editor (opens in a new tab)JavaScript
const order = { item: "tea", qty: 3, note: "hot" };delete order.note;order.note = undefined;console.log(Object.keys(order).join(",")); const scores = [1, , 3];const filled = Array.from({ length: 3 }, () => 0);console.log(1 in scores);console.log(1 in filled);console.log(new Array(3).length, filled.length);

First, the object part:

  • Line 1 makes an order with three properties.
  • Line 2 deletes note. Now the order has no note at all.
  • Line 3 adds note back, with the value undefined.
  • Line 4 prints item,qty,note.
Left: after delete order.note, the order cart holds only item and qty. Right: after order.note = undefined, the order cart holds item, qty and note again, and note is empty.delete order.noteitemqtyordernote is goneorder.note = undefineditemqtynoteordernote is there, but empty
Line 2 takes the name note out of the order. Line 3 puts the name back with the value undefined, so the keys print item,qty,note.

Arrays can have a similar gap, called a hole. A hole is an index with no value at all, not even undefined. Now the array part:

  • On line 6, the two commas side by side in [1, , 3] leave index 1 empty. That is a hole.
  • Line 7 uses Array.from to make an array of length 3. It fills every index with 0.
  • The in operator checks if an index (or a property name) exists. Line 8 prints false, because index 1 of scores is a hole.
  • Line 9 prints true, because index 1 of filled holds a real 0.
  • Line 10 prints 3 3. new Array(3) makes an array of length 3 that holds only holes. filled also has length 3.
Left: scores is 1, a hole, then 3. Index 1 holds nothing, so 1 in scores is false. Right: filled is 0, 0, 0. Index 1 holds a real 0, so 1 in filled is true.scores = [1, , 3]1hole30121 in scoresfalsefilled = [0, 0, 0]0000121 in filledtrue
In scores, the spot at index 1 is empty: no value was ever put there. In filled, every index holds a real 0.

So a hole is not the same as a stored undefined. Reading scores[1] gives undefined, but nothing is stored there. Loops and the engine must handle that missing index in a special way. When an array holds repeated data, fill every index on purpose.

One call, one kind of object

A call site is one place in your code that calls a function, such as total(order). Each time that line runs, the engine makes a note of what it saw. If it keeps seeing the same thing, the engine can make that line faster.

In the code below, the engine keeps notes at two different spots:

  • At the call total(order) on line 8, it notes which function was called. Here it is always total.
  • At the property read order.qty on line 2, it notes the shape of the object. Here it is always "item, then qty".

When a spot sees only one kind of thing, it is called monomorphic, which means "one form". Step through the code to see both spots at work.

Step through: one total call, run twice
Step 0 of 6Ready
Your turn: follow the blue line

A replay of real calls to total. The idea of one shape at one read comes from a simple teaching model of the engine.

Running in
  1. script
Next: line 5
function total(order) {  return order.qty * 10;} const coffee = { item: "coffee", qty: 2 };for (const order of [tea, coffee]) {  console.log(total(order));}
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 loop on lines 7 and 8 calls total(order) twice: first with tea, then with coffee. Both times, the function called is total. Both times, order.qty reads an object that has item then qty. The values differ (3 and 2), but the shape is the same. Tea gives 30 and coffee gives 20.

One function card, total(order), which reads order.qty. Two carts, tea and coffee, both holding item then qty, send arrows up into it. Tea gives 30 and coffee gives 20.total(order)reads order.qtyitemqtyteatotal gives 30same orderitemqtycoffeetotal gives 20
The loop runs the same line, total(order), for tea and then for coffee. Both objects have item then qty, so order.qty sees the same layout each time.

Two more words describe spots that see more variety:

  • Polymorphic ("many forms"): the spot sees a few kinds, such as 2 to 4.
  • Megamorphic ("huge number of forms"): the spot sees a lot of kinds.

At a call, a new kind means a different function. At a property read, a new kind means a different object shape. These words are clues for finding slow code. They are not a reason to remove useful code that works with many kinds of objects.

Playground: more shapes at one property read

At each property read, the engine keeps a small memory called an inline cache. It remembers the shapes it has seen there, and where qty sits in each one. The inline caches lesson explains it in full.

In this playground, change one number: how many different shapes reach the same order.qty read. The labels come from a simple teaching model. They do not come from your real browser.

Playground: how many shapes reach one property read?
One total call and one qty read, run in a loopPop out in the code editor (opens in a new tab)JavaScript
function total(order) {  return order.qty * 10;} const tea = { item: "tea", qty: 3 };const coffee = { item: "coffee", qty: 2 };for (const order of [tea, coffee]) {  console.log(total(order));}
What the read remembersmonomorphic
shapes seen1

shape 1

In this model, a read that sees 1 shape is monomorphic. With 2 to 4 shapes, it is polymorphic and remembers each one. With 5 or more, it is megamorphic. The list above never holds more than 4 shapes.

Try it yourself

In this teaching model, a read that sees 1 different shape is monomorphic.

A simple model of the engine's inline cache, the small memory at each property read. It does not look inside your real engine.

Try 1, then 2, then 5. With 1 shape, the read is monomorphic. With 2 to 4 shapes, it is polymorphic. With 5 or more, it is megamorphic. Real engines choose their own limits. Reset goes back to 1 shape.

Avoid arguments and eval in hot code

Some functions take any number of inputs. For these, use rest parameters: three dots before a name, like ...prices. They collect all the inputs into a normal array.

The older way is the arguments object. Every regular function gets this special list of its inputs. It is harder to read, so don't make it your first choice in new hot code.

Rest parameters collect the prices into an arrayPop out in the code editor (opens in a new tab)JavaScript
function addPrices(...prices) {  return prices.reduce((sum, price) => sum + price, 0);} console.log(addPrices(10, 20, 30));
  • Line 1 collects every input into the array prices.
  • Line 2 adds them up with reduce, starting from 0.
  • Line 5 prints 60.

The code is easy to read, and it doesn't need the special arguments object.

eval runs a string as JavaScript code. That string can read or create local variables. The engine can't know ahead of time what the string will touch. So it has to run the whole function in a slower, more careful way. eval is also a safety risk: if the string comes from a user, the user can run their own code. Avoid it.

eval reads a local variable from a string (only to discuss)JavaScript
function priceWithEval(price) {
  // Only for discussion: the string reads the local variable price.
  return eval("price * 2");
}

console.log(priceWithEval(10));

Line 3 runs the string "price * 2". The string reads the local variable price, so line 6 would print 20. Write price * 2 directly instead.

Proof from the V8 engine

V8 is the JavaScript engine inside Chrome and Node. Its inner workings are not part of JavaScript, and web pages can't see them. But Node can turn on special test functions with the flag --allow-natives-syntax. Their names start with %. This lesson's test uses three of them to check the claims above:

  • %HaveSameMap(a, b) answers: do these two objects share one shape? V8 calls a shape a map.
  • %HasFastProperties(obj) answers: does this object still use the fast, shape-based layout?
  • %HasHoleyElements(arr) answers: has V8 marked this array as one that may have holes?
V8 test functions check the lesson's claims (Node only)JavaScript
class Order {
  constructor(item, qty) {
    this.item = item;
    this.qty = qty;
  }
}
const first = new Order("tea", 3);
const second = new Order("coffee", 2);
const a = {}; a.item = "tea"; a.qty = 3;
const b = {}; b.qty = 3; b.item = "tea";
const changed = { item: "tea", qty: 3, note: "hot" };
delete changed.qty;
console.log("same constructor map", %HaveSameMap(first, second));
console.log("different late maps", %HaveSameMap(a, b));
console.log("fast after delete", %HasFastProperties(changed));
console.log("holey", %HasHoleyElements([1, , 3]));
console.log("packed", %HasHoleyElements([1, 2, 3]));
What the test found

The test runs this code in Node with --allow-natives-syntax. It checks true or false answers, not timings. It found:

  • The two orders made by the constructor share one map.
  • a and b, built in different orders, do not share a map.
  • After deleting qty, which was not the last property, the object loses its fast layout.
  • [1, , 3] is marked as holey, and [1, 2, 3] is not.

Use these test functions only to learn. Never put % functions in real app code. Other engines work in their own way, and V8 itself changes over time.

Habits for real code

Pick one place where data enters the hot code. At that place, clean up the data from the server once. For example, give every order the same fields, in the same order. Then pass these tidy orders into the repeated calculation. This is easier to test than adding special cases inside the loop.

The table below separates four ideas that sound alike. Each row says what the idea means, and what it does not mean.

Four ideas that sound alike
IdeaWhat it meansWhat it does not mean
Stable shapeObjects of one kind get the same property names, added in the same order.It does not promise that a speed test will get faster.
A property set to undefinedThe property is still on the object. Its value is just empty.It is not a deleted property. A deleted property is gone.
Packed arrayEvery index from 0 up to the last one holds a value.It is not an array with holes in it.
MonomorphicA call always runs the same function, or a property read always sees the same shape.It does not mean one single object, used forever.

Use the steps from measuring performance before and after one small change. A tiny speed test of one loop is called a microbenchmark. If it gets faster but no real user action gets faster, the change did not help.

Sort the habits
  • Use one constructor that writes item, then qty.

    It belongs in Keeps repeated work the same. Every new order gets its fields in the same order.

  • Clear an optional note by setting it to undefined.
  • Fill an array with Array.from.
  • Send five differently built objects to one order.qty read.
  • Delete a middle property inside a busy loop.
  • Record a profile of the slow action.
Try it yourself
0 of 6 correct

Put each card in a group: does it keep repeated work the same each time, does it add variety at a hot spot, or is it something to do before you change code?

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

Common misconceptions

  • “Stable shapes make all code faster.” No. They only help where you have measured a slow, repeated piece of work.
  • “You must never use delete.” No. delete is fine when you really mean "this property is gone". Just avoid it inside a hot loop you have measured.
  • “Monomorphic means every object is the same.” No. A call is monomorphic when it always calls the same function. A property read is monomorphic when the objects all have the same shape. Their values can all be different.
  • “The V8 test functions are part of JavaScript.” No. They only exist in V8, behind a Node flag. This lesson uses them only as proof.

The table below lists four terms from this lesson. Each row says what the term means, and what people often mix it up with.

Terms that are easy to mix up
TermWhat it meansNot the same as
Fast pathA quicker way the engine prepares for work it has seen many times.A promise made by the JavaScript language.
ShapeThe engine's note about which property names an object has, and in what order.Just the list that Object.keys prints.
Hot codeCode that runs so often that it shows up when you measure.Every function in an app.
Measure firstMake sure a spot is really slow before you change clear code.A rule against simple habits, like one constructor.

Practice exercises

Exercise 1 · Warm-upPredict the property names

What does this code print? Type the exact output.

Starter codePop out in the code editor (opens in a new tab)JavaScript
const order = { item: "tea", qty: 3 };
console.log(Object.keys(order).join(","));

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

    Exercise 2 · Warm-upSpot the hole

    Does index 1 exist in this array? Type what the code prints.

    Starter codePop out in the code editor (opens in a new tab)JavaScript
    const scores = [1, , 3];
    console.log(1 in scores);

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

      Exercise 3 · PracticePredict total

      What number does this code print?

      Starter codePop out in the code editor (opens in a new tab)JavaScript
      function total(order) { return order.qty * 10; }
      console.log(total({ item: "tea", qty: 3 }));

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

        Exercise 4 · PracticeClear a note

        An order has a note. How would you clear the note without removing the property?

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

          Exercise 5 · ChallengeApply this to a real checkout

          On a shop website, the checkout total takes a long time to appear. What should you do before you rewrite the code?

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

            Exercise 6 · ChallengeName the parameter style

            A new function takes any number of prices. Which kind of parameter is clearer than the arguments object?

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

              Check your understanding

              Read each small piece of code slowly. For each question, first ask what it is about: what the code prints, the engine's teaching model, or when to measure.

              Engine-friendly code quiz · 7 questionsScore: first tries count
              1. Question 1 of 7When should these engine-friendly habits change your code?

                Choose an answer to see the explanation.

              2. Question 2 of 7What does this print?

                Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
                const order = { item: "tea", qty: 3 };
                console.log(Object.keys(order).join(","));

                Choose an answer to see the explanation.

              3. Question 3 of 7Why give an optional field a value in the constructor?

                Choose an answer to see the explanation.

              4. Question 4 of 7What does this code print?

                Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
                const scores = [1, , 3];
                console.log(1 in scores);

                Choose an answer to see the explanation.

              5. Question 5 of 7In this lesson, what makes the call total(order) monomorphic?

                Choose an answer to see the explanation.

              6. Question 6 of 7Why avoid eval in hot code?

                Choose an answer to see the explanation.

              7. Question 7 of 7A page feels slow. What should you do first?

                Choose an answer to see the explanation.

              Key takeaways

              • Measure first. These habits belong only in hot code that you have measured.
              • Build objects of one kind with the same properties, in the same order.
              • Give optional fields a value, such as undefined, at the start. Avoid delete in hot loops.
              • Fill arrays fully. Don't leave holes in repeated data.
              • Let each hot call or property read see only a few kinds of objects.
              • Use rest parameters, and keep eval out of hot code.

              Remember the one-liner.
              When repeated work looks the same each time, the engine can prepare a faster path for it.

              Coming next: Looking inside the engine. You will use tools and flags to see what the engine decides, instead of guessing.

              CompleteFrontend Clear concepts. Working examples.