cf.completefrontend
Open lab My progress
THE JAVASCRIPT FIELD GUIDE

How JavaScript evolves

Learn how TC39 turns an idea into a JavaScript feature: the stages from 0 to 4, stage 2.7 and test262, browser implementations, and Baseline.

By the end, you can
  • 01
    Name the stagesSay what has to be true for a proposal at stage 0, 1, 2, 2.7, 3 and 4.
  • 02
    Read a testRead a test in the style of test262, and say why tests come before browsers ship a feature.
  • 03
    Decide when to use a featureUse a proposal's stage and its Baseline status to decide whether your code can rely on it.

How JavaScript evolves: one proposal at a time

Since 2015, JavaScript gets a new edition every year. Each edition is a collection of small, finished features. Every one of them started as a proposal: a written idea for a change to the language, which a committee checks step by step.

Three features from three recent editionsPop out in the code editor (opens in a new tab)JavaScript
var prices = [3, 4, 5];
console.log(prices.at(-1));
console.log(prices.findLast((p) => p < 5));
console.log(Object.groupBy(prices, (p) => (p < 5 ? "cheap" : "dear")).cheap.length);

Line 1 makes an array of prices. Line 2 uses at(-1), which counts back from the end, and prints 5. It arrived in ES2022.

Line 3 uses findLast, from ES2023, which finds the last price under 5 and prints 4. Line 4 uses Object.groupBy, from ES2024, which sorts the prices into groups. The cheap group holds 3 and 4, so it prints 2.

Each of these three was a proposal for years before it reached your browser. Every proposal climbs the same six steps, called stages.

Six steps rising from left to right: stage 0 idea, stage 1 problem, stage 2 draft, stage 2.7 tests, stage 3 ships, stage 4 done. An arrow at the end points to the next yearly edition.0idea1problem2draft2.7tests3ships4donenextedition
Each proposal climbs the same steps. at, findLast and Object.groupBy from the example each reached stage 4, and then went into the next yearly edition.
Real-life analogyA new bus line for the city

Picture a city adding a bus line. Anyone can suggest one. The council first agrees the need is real, then planners draw a route, and the council approves the final plan. Buses then drive the route for real to find problems. Only when it works does the line go on the printed map.

In real life: Someone suggests a bus line
In JavaScript: Stage 0: an idea
In real life: The council agrees there is a real need
In JavaScript: Stage 1: the problem is accepted
In real life: Planners draw a route, then approve the final plan
In JavaScript: Stages 2 and 2.7: a draft, then approved spec text
In real life: Test drives on the real streets
In JavaScript: Stage 3: browsers build and ship it
In real life: The line goes on the printed city map
In JavaScript: Stage 4: it joins the next edition
The main idea

New JavaScript features are made by TC39, a committee, in public. Each one is a proposal that moves through stages 0, 1, 2, 2.7, 3 and 4. At stage 4, it joins the next yearly edition. You can use it safely once every browser your users have supports it.

TC39 and Ecma: who decides

Ecma International is a standards group. Its Technical Committee 39, TC39, writes the ECMAScript standard, the rulebook for JavaScript. The early editions lesson tells how it started.

TC39 is made of delegates, people sent by its members:

  • browser makers, such as Google, Mozilla, Apple and Microsoft,
  • other companies that use JavaScript a lot,
  • invited experts, such as people who maintain popular tools.

The delegates meet about six times a year. A proposal moves up a stage only by consensus: everyone must agree, or at least not object. One objection is enough to make a proposal wait until the concern is solved.

Three cards, browsers, companies and experts, each send an arrow into a box labeled TC39. An arrow from the box leads to a card: next stage, if nobody objects.browserscompaniesexpertsTC39next stageif nobody objects
Delegates from browser makers, other companies and invited experts meet about six times a year. A proposal moves up a stage only by consensus: if any delegate objects, it waits.

Each proposal has a champion, one or more delegates who write it and move it forward. Every proposal lives in a public repository on GitHub, so anyone can read it and comment. Many proposals start from code that people already write by hand again and again.

A helper people wrote by hand, and the feature that replaced itPop out in the code editor (opens in a new tab)JavaScript
function lastItem(list) {
  return list[list.length - 1];
}
console.log(lastItem([3, 4, 5]));
console.log([3, 4, 5].at(-1));

Lines 1 to 3 make lastItem, a helper that reads the last item of an array. Line 4 prints 5.

Code like this appeared in project after project, and that was part of the case for the at proposal. Line 5 does the same job with at(-1), and prints 5 too.

Stages 0 to 4: from an idea to the standard

The stages are written down in the TC39 process document. Read each row of the table as: "at this stage, the proposal means this, and to get here it needs this."

The six stages of a TC39 proposal
The stageWhat it meansWhat it needs to get there
0An idea. Anyone can write one down.Nothing. It doesn't need the committee's approval.
1The committee agrees the problem is worth solving.A champion, a description of the problem and a rough solution, and a public repository.
2The committee has picked a solution, but its details may still change.First spec text covering the main parts. TODO notes are allowed.
2.7The design is approved. Only tests and real use can change it now.Complete spec text, signed off by the chosen reviewers and the spec editors.
3Browsers are asked to build and ship it.Enough tests, usually in test262.
4Finished. It goes into the next yearly edition.Two engines that pass the tests, real use in the field, and an approved change to the spec.

A proposal can stop at any stage. Some stay at stage 1 for years, and some are withdrawn. Only stage 4 means a proposal is finished.

Stage 3 is when engines build the feature, so it often reaches browsers before stage 4. A program can check whether the browser running it has a feature with typeof, which gives "undefined" for something that doesn't exist.

Which recent features does this browser have?Pop out in the code editor (opens in a new tab)JavaScript
var checks = {
  "Array.prototype.at": typeof [].at,
  "Object.groupBy": typeof Object.groupBy,
  "Promise.withResolvers": typeof Promise.withResolvers,
};
for (var name in checks) {
  console.log(name + ": " + (checks[name] === "function" ? "built in" : "missing"));
}

Lines 1 to 5 make an object with the typeof of three features that reached stage 4 in recent years. Lines 6 to 8 loop over the names.

In any current browser, all three lines say built in. In a browser from before those features shipped, they would say missing.

Stage 2.7 and test262: tests before shipping

Stage 2.7 is the newest stage. The committee added it between stage 2 and stage 3, so that a feature's tests are written before browsers are asked to build it. Then every engine checks its work against the same tests.

Those tests go into test262, the shared test suite for JavaScript engines. The test262 lesson covers it in depth. A test runs some code and checks the result.

Three tests for at(), in the style of test262Pop out in the code editor (opens in a new tab)JavaScript
function assertSame(actual, expected, message) {
  if (actual !== expected) {
    throw new Error(message + ": expected " + expected + ", got " + actual);
  }
}
assertSame([3, 4, 5].at(0), 3, "at(0) reads the first item");
assertSame([3, 4, 5].at(-1), 5, "at(-1) reads the last item");
assertSame([3, 4, 5].at(3), undefined, "at(3) is past the end");
console.log("3 tests passed");

Lines 1 to 5 define assertSame. If the two values differ, line 3 throws an error, and a thrown error means the test failed.

Lines 6 to 8 are three tests: the first item, the last item, and a position past the end, which must give undefined. None throws, so line 9 prints 3 tests passed.

Step through three tests for at()
Step 0 of 8Ready
Your turn: follow the blue line

A replay of the example code, not a view inside a JavaScript engine. The tests run for real.

Running in
  1. script
Next: line 1
  if (actual !== expected) {    throw new Error(message + ": expected " + expected + ", got " + actual);  }}assertSame([3, 4, 5].at(0), 3, "at(0) reads the first item");assertSame([3, 4, 5].at(-1), 5, "at(-1) reads the last item");assertSame([3, 4, 5].at(3), undefined, "at(3) is past the end");console.log("3 tests passed");
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.

Press Step to run each test and watch the count of passed tests beside line 9. This replays the example code; it isn't a view inside the JavaScript engine.

Here is what a failing test looks like.

A test with the wrong expected valuePop out in the code editor (opens in a new tab)JavaScript
function assertSame(actual, expected, message) {
  if (actual !== expected) {
    throw new Error(message + ": expected " + expected + ", got " + actual);
  }
}
try {
  assertSame([3, 4, 5].at(-1), 3, "at(-1) reads the last item");
} catch (error) {
  console.log(error.message);
}

Line 7 expects at(-1) to give 3. It gives 5, so assertSame throws. Line 9 prints the message: at(-1) reads the last item: expected 3, got 5.

Real test262 files look much the same. They start with a comment block that says which part of the spec they test.

A test file in the style of test262JavaScript
/*---
esid: sec-array.prototype.at
description: at() with a negative index counts back from the end
features: [Array.prototype.at]
---*/
var prices = [3, 4, 5];
assert.sameValue(prices.at(-1), 5, "prices.at(-1) is 5");
assert.sameValue(prices.at(-3), 3, "prices.at(-3) is 3");

Lines 1 to 5 are the comment block. Line 2 names the section of the spec, and line 4 names the feature. Lines 7 and 8 use assert.sameValue, which test262's own helper files provide. That is why this box has no Run button.

A card spec text points to a card test262 tests. Three arrows go from the tests to three engine cards: V8, SpiderMonkey and JavaScriptCore. Under the engines: same tests, same answers.spec texttest262 testsV8SpiderMonkeyJavaScriptCoresame tests, same answers
At stage 2.7, the spec text is finished, and tests like the ones in the example are written for test262. Every engine runs the same tests, so at(-1) gives 5 everywhere.

Implementations and Baseline: when you can use it

An implementation is an engine that has built the feature. To reach stage 4, a proposal needs two engines that pass its tests, and real use in the field. Then it joins the draft spec, and the next edition is approved each summer.

But stage 4 doesn't tell you whether your users can run the feature. Browsers ship at different times, and people update at different times. Baseline answers that question. It is a status shown on MDN and other sites, based on the core browsers: Chrome, Edge, Firefox and Safari, on desktop and mobile.

Read each row of the table as: "a feature with this status is supported like this, so you can use it like this."

The three Baseline statuses
The statusWhat it meansWhat it means for your code
Limited availabilityAt least one core browser doesn't support it yet.Use it only with a fallback or a polyfill.
Newly availableThe current version of every core browser supports it.Fine for many projects, but people with older browsers may not have it.
Widely availableIt has been newly available for 30 months.Safe to use without a fallback for almost everyone.

Here is a small model of Baseline, written for this lesson, with today fixed at October 2026.

A small model of BaselinePop out in the code editor (opens in a new tab)JavaScript
function monthsBetween(from, to) {
  return (to.year - from.year) * 12 + (to.month - from.month);
}
function baseline(shipped, today) {
  if (shipped.includes(null)) return "limited availability";
  var last = shipped.reduce((a, b) => (monthsBetween(a, b) > 0 ? b : a));
  return monthsBetween(last, today) >= 30 ? "widely available" : "newly available";
}
var today = { year: 2026, month: 10 };
console.log(baseline([{ year: 2023, month: 9 }, { year: 2023, month: 10 }, { year: 2024, month: 3 }], today));
console.log(baseline([{ year: 2025, month: 6 }, { year: 2026, month: 1 }, { year: 2026, month: 2 }], today));
console.log(baseline([{ year: 2025, month: 6 }, null, { year: 2026, month: 2 }], today));

Lines 1 to 3 count the months between two dates. Lines 4 to 8 work out the status. If any browser hasn't shipped it, line 5 returns limited availability.

Otherwise, line 6 finds the date of the last browser to ship it, and line 7 checks whether 30 months have passed since then. Line 9 sets today.

Line 10 lists three ship dates, the last in March 2024. That was 31 months ago, so it prints widely available. Line 11's last date is February 2026, only 8 months ago, so it prints newly available. Line 12 has a null, so it prints limited availability.

Three cards for Chrome and Edge, Firefox and Safari, each with the month it shipped a feature: September 2023, October 2023 and March 2024. An arrow from the last one leads to newly available, March 2024, then another arrow labeled 30 months leads to widely available, September 2026.Chrome: 2023-9Firefox: 2023-10Safari: 2024-3the last one to shipnewly: 2024-330 monthswidely: 2026-9
In line 10 of the example, the last browser ships in March 2024, so the feature is newly available then. Thirty months later, in September 2026, it is widely available, so line 10 prints widely available in October 2026.

Following and trying proposals

You don't have to wait for a new edition to learn about a feature. The list of active proposals, with their stages, is public at github.com/tc39/proposals. Each proposal's repository explains the problem, shows examples, and links to discussions.

You can try a proposal in three ways:

  • a polyfill, code that adds a missing built-in feature, for methods and objects,
  • a compiler such as Babel or TypeScript, which turns new syntax into older syntax,
  • a browser or Node.js setting that turns on an unfinished feature, for testing only.

Polyfills for stage 3 features are common, because the design is stable. A careful polyfill adds the feature only if the browser doesn't have it.

A careful polyfill for findLastPop out in the code editor (opens in a new tab)JavaScript
if (typeof Array.prototype.findLast !== "function") {
  Object.defineProperty(Array.prototype, "findLast", {
    value: function (test) {
      for (var i = this.length - 1; i >= 0; i--) {
        if (test(this[i])) return this[i];
      }
      return undefined;
    },
    writable: true,
    configurable: true,
  });
}
console.log([3, 4, 5].findLast((p) => p < 5));

Line 1 checks whether findLast already exists. In current browsers it does, so lines 2 to 11 are skipped. In an older browser, lines 2 to 11 add it, and lines 4 to 6 loop from the last item back to the first.

Line 13 prints 4 either way. Avoid polyfills for proposals below stage 3: their design can still change, and your code would depend on the old design.

The playground runs the Baseline model with ship dates you choose. Pick the year each browser shipped a feature. Guess the status before you look.

Playground: when is a feature safe to use?
The Baseline model, with your dates on line 10Pop out in the code editor (opens in a new tab)JavaScript
function monthsBetween(from, to) {  return (to.year - from.year) * 12 + (to.month - from.month);}function baseline(shipped, today) {  if (shipped.includes(null)) return "limited availability";  var last = shipped.reduce((a, b) => (monthsBetween(a, b) > 0 ? b : a));  return monthsBetween(last, today) >= 30 ? "widely available" : "newly available";}var today = { year: 2026, month: 10 };console.log(baseline([{ year: 2023, month: 6 }, { year: 2023, month: 6 }, { year: 2024, month: 6 }], today));
What line 10 printsnewly available
Chrome and EdgeJune 2023
FirefoxJune 2023
SafariJune 2024
Try it yourself
Chrome and Edge shipped it:
Firefox shipped it:
Safari shipped it:
Newly available.

The last browser shipped it in June 2024, less than 30 months before October 2026. It works in every current browser, but people with older versions may not have it yet.

The program runs for real. It uses the lesson's small model of Baseline, with today fixed at October 2026. The real rule also checks long-term support releases.

The starting dates give newly available: Safari shipped in June 2024, only 28 months before October 2026. Move Safari to June 2023 and it becomes widely available. Pick "not yet" for any browser and it becomes limited availability.

Which stage is the proposal at?
  • Anyone wrote down an idea; the committee hasn't looked at it
  • A champion and a description of the problem
  • First spec text, with some TODO notes
  • Complete spec text, signed off by reviewers and editors
  • Writing the tests is the work happening now
  • Browsers are building it and finding problems
  • Two engines pass the tests, and it is used in the field
  • It is merged into the next yearly edition
Try it yourself
0 of 8 correct

Put each description with the stage it belongs to: stage 0 or 1, stage 2 or 2.7, stage 3, or stage 4.

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

Common mix-ups

Most mix-ups come from treating stage 3 as finished, or stage 4 as "in every browser".

  • "Stage 3 means the feature is in the standard." No. It joins the standard at stage 4.
  • "Stage 4 means every browser has it." No. It needs two engines. Check Baseline for your users' browsers.
  • "One company decides what goes into JavaScript." No. TC39 decides by consensus, and any delegate can object.
  • "Every proposal eventually ships." No. Many stop at stage 1 or 2, and some are withdrawn.
  • "Stage 2.7 is a typo for stage 2 or 3." No. It is a real stage, for writing tests before browsers build the feature.
  • "Baseline is a TC39 stage." No. Baseline is about which browsers support a feature, not about the standard.

The table below puts three of these side by side. Read each row as: "people often think this, but this is true."

What is true about three ideas from this lesson
The topicWhat people thinkWhat is true
Stage 3The feature is finished and in the standard.It is still being implemented. It joins the standard at stage 4.
Stage 4Every browser supports it.Two engines support it. Others may still be working on it.
A proposalOne company decides whether it happens.The whole committee decides by consensus.

Practice exercises

Every program below is a sloppy classic script. Guess first, then run it.

Exercise 1 · Warm-upPredict which features exist

What does line 1 print?

Starter codePop out in the code editor (opens in a new tab)JavaScript
console.log(typeof [].at, typeof [].findLast, typeof [].flatten);

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

    Exercise 2 · Warm-upFind the bug in a test

    What does the program print? Is the bug in the engine or in the test? Fix it so it prints passed.

    Starter codePop out in the code editor (opens in a new tab)JavaScript
    function assertSame(actual, expected, message) {
      if (actual !== expected) {
        throw new Error(message + ": expected " + expected + ", got " + actual);
      }
    }
    try {
      assertSame([3, 4, 5].findLast((p) => p < 5), 3, "findLast finds the last match");
      console.log("passed");
    } catch (error) {
      console.log(error.message);
    }

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

      Exercise 3 · PracticeFind the bug in a careless polyfill

      What does line 4 print? Then fix the polyfill so it never replaces a built-in at.

      Starter codePop out in the code editor (opens in a new tab)JavaScript
      Array.prototype.at = function (index) {
        return this[index];
      };
      console.log([3, 4, 5].at(-1));

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

        Exercise 4 · PracticeCount the months to widely available

        What does line 4 print?

        Starter codePop out in the code editor (opens in a new tab)JavaScript
        function monthsBetween(from, to) {
          return (to.year - from.year) * 12 + (to.month - from.month);
        }
        console.log(monthsBetween({ year: 2024, month: 3 }, { year: 2026, month: 9 }));

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

          Exercise 5 · ChallengeWrite two tests for Object.groupBy

          Replace line 7 with two tests, in the style of test262, that check the groups. What does your program print when both pass?

          Starter codePop out in the code editor (opens in a new tab)JavaScript
          function assertSame(actual, expected, message) {
            if (actual !== expected) {
              throw new Error(message + ": expected " + expected + ", got " + actual);
            }
          }
          var groups = Object.groupBy([3, 4, 5], (p) => (p % 2 === 0 ? "even" : "odd"));
          // Write two tests here.
          console.log("done");

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

            Exercise 6 · ChallengePredict the widely available date

            A feature became newly available in March 2024. What does line 7 print?

            Starter codePop out in the code editor (opens in a new tab)JavaScript
            function addMonths(date, months) {
              var total = date.year * 12 + (date.month - 1) + months;
              return { year: Math.floor(total / 12), month: (total % 12) + 1 };
            }
            var newly = { year: 2024, month: 3 };
            var widely = addMonths(newly, 30);
            console.log(widely.year + "-" + widely.month);

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

              Check your understanding

              For each question, ask which stage the proposal is at, and what that stage needs. For Baseline, count the months. Each answer explains why it's right or wrong.

              The TC39 process quiz · 6 questionsScore: first tries count
              1. Question 1 of 6Which stage means browsers are asked to build and ship a proposal?

                Choose an answer to see the explanation.

              2. Question 2 of 6What is stage 2.7 for?

                Choose an answer to see the explanation.

              3. Question 3 of 6What does this failing test print?

                Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
                function assertSame(actual, expected) {
                  if (actual !== expected) throw new Error("expected " + expected + ", got " + actual);
                }
                try {
                  assertSame([3, 4].at(-2), 4);
                } catch (error) {
                  console.log(error.message);
                }

                Choose an answer to see the explanation.

              4. Question 4 of 6How does TC39 decide to move a proposal to the next stage?

                Choose an answer to see the explanation.

              5. Question 5 of 6Is a feature that became newly available in June 2024 widely available in October 2026?

                Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
                function monthsBetween(from, to) {
                  return (to.year - from.year) * 12 + (to.month - from.month);
                }
                console.log(monthsBetween({ year: 2024, month: 6 }, { year: 2026, month: 10 }) >= 30);

                Choose an answer to see the explanation.

              6. Question 6 of 6What does it mean that a feature is newly available in Baseline?

                Choose an answer to see the explanation.

              Key takeaways

              JavaScript grows in public, one small proposal at a time. Knowing the stages tells you how close a feature is, and Baseline tells you when your users have it.

              • TC39, a committee of Ecma, writes the JavaScript standard. Its delegates decide by consensus.
              • Every new feature is a proposal with a champion and a public repository.
              • The stages are 0 (idea), 1 (problem), 2 (draft), 2.7 (tests), 3 (ships) and 4 (done).
              • At stage 2.7 the design is approved and tests are written for test262, before browsers build it.
              • Stage 4 needs two engines that pass the tests. Then the feature joins the next yearly edition.
              • Baseline says when browsers support a feature: limited, newly available, then widely available 30 months later.
              • Try proposals with polyfills or compilers, and avoid relying on anything below stage 3.

              Remember the one-liner.
              A proposal climbs from 0 to 4 by consensus, tests come before browsers, and Baseline tells you when your users have it.

              Coming next: Modern JavaScript: ES2015 to today. It goes through what each yearly edition added, feature by feature.

              CompleteFrontend Clear concepts. Working examples.