{"id":4144,"date":"2024-10-17T17:00:55","date_gmt":"2024-10-17T17:00:55","guid":{"rendered":"https:\/\/algocademy.com\/blog\/javascript-versions-a-comprehensive-guide-to-the-evolution-of-ecmascript\/"},"modified":"2026-10-15T12:51:17","modified_gmt":"2026-10-15T12:51:17","slug":"javascript-versions-a-comprehensive-guide-to-the-evolution-of-ecmascript","status":"publish","type":"post","link":"https:\/\/algocademy.com\/blog\/javascript-versions-a-comprehensive-guide-to-the-evolution-of-ecmascript\/","title":{"rendered":"JavaScript Versions: ECMAScript Editions from ES1 to ES2026"},"content":{"rendered":"<p>JavaScript is the language; ECMAScript is the standard that defines it. Each edition of the standard (ECMA-262) is a &#8220;JavaScript version&#8221;. Since ES2015 a new edition is approved every June, and the latest finished one is <strong>ECMAScript 2026 (the 17th edition, June 2026)<\/strong>. Names like &#8220;ES6&#8221; and &#8220;ES7&#8221; are older numbering for ES2015 and ES2016, so ES7 was released in June 2016.<\/p>\n<p>The sections below list every edition, when it came out, and what a working developer should know about it. The code examples were run on Node.js 22, so features newer than that are shown without output.<\/p>\n<h2>ECMAScript editions at a glance<\/h2>\n<p>Dates are the editions listed on <a href=\"https:\/\/ecma-international.org\/publications-and-standards\/standards\/ecma-262\/\">ecma-international.org<\/a>.<\/p>\n<table>\n<thead>\n<tr>\n<th>Edition<\/th>\n<th>Name<\/th>\n<th>Released<\/th>\n<th>Headline additions<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>1<\/td>\n<td>ES1<\/td>\n<td>June 1997<\/td>\n<td>First standard<\/td>\n<\/tr>\n<tr>\n<td>2<\/td>\n<td>ES2<\/td>\n<td>1998<\/td>\n<td>Editorial alignment with ISO\/IEC 16262<\/td>\n<\/tr>\n<tr>\n<td>3<\/td>\n<td>ES3<\/td>\n<td>December 1999<\/td>\n<td>Regular expressions, try\/catch, do-while, switch<\/td>\n<\/tr>\n<tr>\n<td>4<\/td>\n<td>ES4<\/td>\n<td>never published<\/td>\n<td>Abandoned<\/td>\n<\/tr>\n<tr>\n<td>5 \/ 5.1<\/td>\n<td>ES5<\/td>\n<td>December 2009 \/ June 2011<\/td>\n<td>Strict mode, JSON, array methods<\/td>\n<\/tr>\n<tr>\n<td>6<\/td>\n<td>ES2015 (ES6)<\/td>\n<td>June 2015<\/td>\n<td>let\/const, classes, modules, arrow functions, promises<\/td>\n<\/tr>\n<tr>\n<td>7<\/td>\n<td>ES2016 (ES7)<\/td>\n<td>June 2016<\/td>\n<td><code>**<\/code>, <code>Array.prototype.includes<\/code><\/td>\n<\/tr>\n<tr>\n<td>8<\/td>\n<td>ES2017<\/td>\n<td>June 2017<\/td>\n<td>async\/await, <code>Object.entries<\/code><\/td>\n<\/tr>\n<tr>\n<td>9<\/td>\n<td>ES2018<\/td>\n<td>June 2018<\/td>\n<td>Object rest\/spread, async iteration, <code>Promise.finally<\/code><\/td>\n<\/tr>\n<tr>\n<td>10<\/td>\n<td>ES2019<\/td>\n<td>June 2019<\/td>\n<td><code>flat<\/code>, <code>flatMap<\/code>, <code>Object.fromEntries<\/code><\/td>\n<\/tr>\n<tr>\n<td>11<\/td>\n<td>ES2020<\/td>\n<td>June 2020<\/td>\n<td><code>?.<\/code>, <code>??<\/code>, BigInt, <code>Promise.allSettled<\/code><\/td>\n<\/tr>\n<tr>\n<td>12<\/td>\n<td>ES2021<\/td>\n<td>June 2021<\/td>\n<td><code>replaceAll<\/code>, <code>??=<\/code>, <code>Promise.any<\/code><\/td>\n<\/tr>\n<tr>\n<td>13<\/td>\n<td>ES2022<\/td>\n<td>June 2022<\/td>\n<td>Class fields, private fields, top-level await, <code>at()<\/code><\/td>\n<\/tr>\n<tr>\n<td>14<\/td>\n<td>ES2023<\/td>\n<td>June 2023<\/td>\n<td><code>toSorted<\/code>, <code>findLast<\/code>, hashbang<\/td>\n<\/tr>\n<tr>\n<td>15<\/td>\n<td>ES2024<\/td>\n<td>June 2024<\/td>\n<td><code>Object.groupBy<\/code>, <code>Promise.withResolvers<\/code><\/td>\n<\/tr>\n<tr>\n<td>16<\/td>\n<td>ES2025<\/td>\n<td>June 2025<\/td>\n<td>Set methods, iterator helpers, <code>Promise.try<\/code><\/td>\n<\/tr>\n<tr>\n<td>17<\/td>\n<td>ES2026<\/td>\n<td>June 2026<\/td>\n<td><code>Array.fromAsync<\/code>, <code>Error.isError<\/code>, <code>Math.sumPrecise<\/code><\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<h2>JavaScript vs ECMAScript<\/h2>\n<p>JavaScript was created at Netscape in 1995. It was handed to Ecma International for standardization, and because &#8220;JavaScript&#8221; was a trademark, the standard got the name ECMAScript. Browsers and runtimes (V8 in Chrome and Node.js, SpiderMonkey in Firefox, JavaScriptCore in Safari) are separate implementations that ship a feature on their own schedule.<\/p>\n<p>Two things follow from that. A feature is &#8220;in JavaScript&#8221; for practical purposes only when the engines you target support it, so check the compatibility tables on <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/JavaScript\">MDN<\/a>. And a proposal becomes part of an edition only when it reaches stage 4 in TC39, the committee that maintains the language. Proposals that are still at earlier stages (decorators, pattern matching) are not part of any edition.<\/p>\n<h2>How the naming changed<\/h2>\n<p>Editions 1 to 6 were numbered. ES4 was abandoned, which is why ES5 follows ES3. With the 6th edition the committee switched to a yearly release and named editions by year, so ES6 became <strong>ES2015<\/strong>, ES7 became <strong>ES2016<\/strong>, and so on. Both names are still in use, but the year form is the official one. When someone says &#8220;ES7&#8221;, they mean ES2016, which is a small release with two features.<\/p>\n<h2>ES1 to ES3 (1997 to 1999)<\/h2>\n<p>ES1 defined the basic syntax, types and core objects such as <code>Array<\/code>, <code>Date<\/code> and <code>Math<\/code>. ES2 was an editorial update. ES3 added regular expressions, <code>try\/catch<\/code>, <code>do-while<\/code> and <code>switch<\/code>. For years, ES3 was what every browser could be trusted to run.<\/p>\n<h2>ES5 (2009, revised 2011)<\/h2>\n<p>ES5 arrived after a decade without a new edition. It added strict mode (<code>\"use strict\"<\/code>), the built-in <code>JSON<\/code> object, the array methods <code>forEach<\/code>, <code>map<\/code>, <code>filter<\/code>, <code>reduce<\/code>, <code>some<\/code> and <code>every<\/code>, <code>Object.keys<\/code>, <code>Object.create<\/code>, <code>Object.freeze<\/code> and <code>Function.prototype.bind<\/code>. It is the baseline that legacy code and old browsers still target.<\/p>\n<h2>ES2015 (ES6): the big one<\/h2>\n<p>ES2015 changed how JavaScript is written. It added <code>let<\/code> and <code>const<\/code>, arrow functions, classes, template literals, destructuring, default and rest parameters, the spread operator, <code>Promise<\/code>, <code>Map<\/code> and <code>Set<\/code>, <code>Symbol<\/code>, iterators and generators, and the <code>import<\/code>\/<code>export<\/code> module syntax. If you only learn one edition well, learn this one. The lesson on <a href=\"https:\/\/algocademy.com\/link\/?lang=js&amp;problem=constants&amp;solution=1\">constants<\/a> covers the <code>const<\/code> part.<\/p>\n<pre><code class=\"language-javascript\">const greet = name =&gt; `Hello, ${name}!`;\nconst { name, ...rest } = { name: \"Alice\", age: 30, city: \"Oslo\" };\n\nconsole.log(greet(\"Alice\"), name, rest);\n\/\/ Hello, Alice! Alice { age: 30, city: 'Oslo' }<\/code><\/pre>\n<p>(The object form of <code>...rest<\/code> used here arrived later, in ES2018; array rest and spread are ES2015.)<\/p>\n<h2>ES2016 (ES7): two features<\/h2>\n<p>The exponentiation operator <code>**<\/code> and <code>Array.prototype.includes()<\/code>. That is the whole edition, which is why &#8220;ES7 features&#8221; searches turn up so little.<\/p>\n<pre><code class=\"language-javascript\">console.log(2 ** 3, [1, 2, 3].includes(2));\n\/\/ 8 true<\/code><\/pre>\n<h2>ES2017 (ES8): async\/await<\/h2>\n<p><code>async<\/code>\/<code>await<\/code> lets you write promise-based code in a straight line. The edition also added <code>Object.values()<\/code>, <code>Object.entries()<\/code>, <code>padStart()<\/code>, <code>padEnd()<\/code>, <code>Object.getOwnPropertyDescriptors()<\/code>, trailing commas in function parameter lists, and <code>SharedArrayBuffer<\/code>\/<code>Atomics<\/code>.<\/p>\n<pre><code class=\"language-javascript\">console.log(Object.entries({ a: 1, b: 2 }), \"7\".padStart(3, \"0\"));\n\/\/ [ [ 'a', 1 ], [ 'b', 2 ] ] 007<\/code><\/pre>\n<p>For how promises and the event loop actually run this code, see <a href=\"https:\/\/algocademy.com\/blog\/mastering-javascript-promise-unlocking-asynchronous-programming-for-modern-web-development\/\">the JavaScript Promise guide<\/a> and <a href=\"https:\/\/algocademy.com\/blog\/understanding-the-event-loop-in-javascript-a-comprehensive-guide\/\">the event loop guide<\/a>.<\/p>\n<h2>ES2018 (ES9)<\/h2>\n<p>Rest and spread properties for objects, asynchronous iteration (<code>for await...of<\/code>), <code>Promise.prototype.finally()<\/code>, and regex improvements: named capture groups, lookbehind, the <code>s<\/code> (dotAll) flag and Unicode property escapes.<\/p>\n<h2>ES2019 (ES10)<\/h2>\n<p><code>Array.prototype.flat()<\/code> and <code>flatMap()<\/code>, <code>Object.fromEntries()<\/code>, <code>trimStart()<\/code> and <code>trimEnd()<\/code>, optional <code>catch<\/code> binding (<code>catch { }<\/code> without a variable), and a requirement that <code>Array.prototype.sort<\/code> be stable.<\/p>\n<pre><code class=\"language-javascript\">console.log([1, [2, [3, [4]]]].flat(Infinity), Object.fromEntries([[\"a\", 1]]));\n\/\/ [ 1, 2, 3, 4 ] { a: 1 }<\/code><\/pre>\n<h2>ES2020 (ES11)<\/h2>\n<p>Optional chaining <code>?.<\/code>, nullish coalescing <code>??<\/code>, <code>BigInt<\/code>, <code>Promise.allSettled()<\/code>, <code>globalThis<\/code>, dynamic <code>import()<\/code> and <code>String.prototype.matchAll()<\/code>. The difference between <code>??<\/code> and <code>||<\/code> matters: <code>??<\/code> only falls back on <code>null<\/code> and <code>undefined<\/code>, so <code>0<\/code> and <code>\"\"<\/code> are kept.<\/p>\n<pre><code class=\"language-javascript\">console.log(null ?? \"default\", 0 ?? 42, ({ a: {} }).a?.b?.c, 2n ** 64n);\n\/\/ default 0 undefined 18446744073709551616n<\/code><\/pre>\n<h2>ES2021 (ES12)<\/h2>\n<p>Logical assignment operators (<code>||=<\/code>, <code>&amp;&amp;=<\/code>, <code>??=<\/code>), numeric separators, <code>String.prototype.replaceAll()<\/code>, <code>Promise.any()<\/code>, and <code>WeakRef<\/code> with <code>FinalizationRegistry<\/code>.<\/p>\n<pre><code class=\"language-javascript\">let a = null;\na ??= 10;\nconsole.log(a, \"a-b-c\".replaceAll(\"-\", \"_\"), 1_000_000);\n\/\/ 10 a_b_c 1000000<\/code><\/pre>\n<h2>ES2022 (ES13)<\/h2>\n<p>Class fields, private fields and methods (<code>#name<\/code>), static class fields and static blocks, top-level <code>await<\/code> in modules, <code>Array.prototype.at()<\/code>, <code>Object.hasOwn()<\/code>, the <code>cause<\/code> option for errors, and the regex <code>d<\/code> flag (match indices).<\/p>\n<pre><code class=\"language-javascript\">class Counter {\n  #n = 0;\n  static label = \"counter\";\n  inc() { this.#n++; return this.#n; }\n}\n\nconsole.log(new Counter().inc(), Counter.label, [1, 2, 3].at(-1), Object.hasOwn({ x: 1 }, \"x\"));\n\/\/ 1 counter 3 true\n\nconst err = new Error(\"outer\", { cause: new Error(\"inner\") });\nconsole.log(err.cause.message);\n\/\/ inner<\/code><\/pre>\n<p>Top-level await only works in ES modules (a <code>.mjs<\/code> file, or <code>\"type\": \"module\"<\/code> in <code>package.json<\/code>):<\/p>\n<pre><code class=\"language-javascript\">const data = await Promise.resolve({ ok: true });\nconsole.log(data);\n\/\/ { ok: true }<\/code><\/pre>\n<h2>ES2023 (14th edition)<\/h2>\n<p>The main addition is the &#8220;change array by copy&#8221; group: <code>toSorted()<\/code>, <code>toReversed()<\/code>, <code>toSpliced()<\/code> and <code>with()<\/code>. They return a new array instead of mutating the original, which is what you want in React state or any code that avoids shared mutation. The edition also added <code>findLast()<\/code> and <code>findLastIndex()<\/code>, hashbang (<code>#!<\/code>) comment syntax, and symbols as <code>WeakMap<\/code> keys.<\/p>\n<pre><code class=\"language-javascript\">const nums = [3, 1, 2];\nconsole.log(nums.toSorted(), nums, nums.toReversed(), nums.with(0, 9), nums.findLast(n =&gt; n &gt; 1));\n\/\/ [ 1, 2, 3 ] [ 3, 1, 2 ] [ 2, 1, 3 ] [ 9, 1, 2 ] 2<\/code><\/pre>\n<h2>ES2024 (15th edition)<\/h2>\n<p>Grouping with <code>Object.groupBy()<\/code> and <code>Map.groupBy()<\/code>, <code>Promise.withResolvers()<\/code>, resizable and transferable <code>ArrayBuffer<\/code>s, <code>Atomics.waitAsync()<\/code>, <code>isWellFormed()<\/code> \/ <code>toWellFormed()<\/code> on strings, and the regex <code>v<\/code> flag.<\/p>\n<pre><code class=\"language-javascript\">const people = [{ n: \"A\", age: 30 }, { n: \"B\", age: 25 }, { n: \"C\", age: 30 }];\nconsole.log(Object.groupBy(people, p =&gt; p.age));\n\/\/ [Object: null prototype] {\n\/\/   '25': [ { n: 'B', age: 25 } ],\n\/\/   '30': [ { n: 'A', age: 30 }, { n: 'C', age: 30 } ]\n\/\/ }\n\nconsole.log(\"ab\\uD800\".isWellFormed());\n\/\/ false<\/code><\/pre>\n<p><code>Object.groupBy<\/code> returns an object with a <code>null<\/code> prototype, so it has no inherited keys like <code>toString<\/code> to collide with your group names.<\/p>\n<h2>ES2025 (16th edition)<\/h2>\n<p>New <code>Set<\/code> methods (<code>union<\/code>, <code>intersection<\/code>, <code>difference<\/code>, <code>symmetricDifference<\/code>, <code>isSubsetOf<\/code> and friends), iterator helpers (<code>map<\/code>, <code>filter<\/code>, <code>take<\/code>, <code>drop<\/code>, <code>toArray<\/code> and more on iterators), <code>Promise.try()<\/code>, <code>RegExp.escape()<\/code>, regex modifiers and duplicate named capture groups, JSON modules with import attributes, and <code>Float16Array<\/code>.<\/p>\n<pre><code class=\"language-javascript\">console.log(new Set([1, 2, 3]).union(new Set([3, 4])));\n\/\/ Set(4) { 1, 2, 3, 4 }\n\nconsole.log([1, 2, 3, 4].values().filter(n =&gt; n % 2 === 0).map(n =&gt; n * 10).toArray());\n\/\/ [ 20, 40 ]<\/code><\/pre>\n<p>Iterator helpers are lazy: they process one item at a time instead of building intermediate arrays, which helps with large or infinite sequences.<\/p>\n<h2>ES2026 (17th edition)<\/h2>\n<p>The edition approved in June 2026 adds, among other things:<\/p>\n<ul>\n<li><code>Array.fromAsync()<\/code>: build an array from an async iterable.<\/li>\n<li><code>Error.isError()<\/code>: reliably tell whether a value is an error, including errors created in another iframe or VM context, where <code>instanceof Error<\/code> fails.<\/li>\n<li><code>Math.sumPrecise()<\/code>: sum numbers without accumulating floating-point error.<\/li>\n<li><code>Iterator.concat()<\/code>: chain several iterators into one.<\/li>\n<li><code>Uint8Array<\/code> conversions to and from Base64 and hex (<code>toBase64()<\/code>, <code>fromBase64()<\/code>, <code>toHex()<\/code>, <code>fromHex()<\/code>).<\/li>\n<li><code>JSON.rawJSON()<\/code> and <code>JSON.isRawJSON()<\/code>, plus access to the source text in <code>JSON.parse<\/code> revivers, which lets you keep large integers exact.<\/li>\n<li><code>getOrInsert()<\/code> and <code>getOrInsertComputed()<\/code> on <code>Map<\/code> and <code>WeakMap<\/code>.<\/li>\n<\/ul>\n<pre><code class=\"language-javascript\">const rows = await Array.fromAsync((async function* () { yield 1; yield 2; })());\nconsole.log(rows);\n\/\/ [ 1, 2 ]<\/code><\/pre>\n<p>The list above was checked against the text of the ECMAScript 2026 specification at <a href=\"https:\/\/262.ecma-international.org\/17.0\/\">262.ecma-international.org\/17.0<\/a>, and ran <code>Array.fromAsync<\/code> on Node.js 22. Runtime support for the rest varies, so check MDN&#8217;s compatibility tables before using them without a fallback.<\/p>\n<h2>What comes next<\/h2>\n<p>The TC39 list of <a href=\"https:\/\/github.com\/tc39\/proposals\/blob\/main\/finished-proposals.md\">finished proposals<\/a> shows what is already stage 4 and waiting for the next edition. As of October 2026 it includes Temporal (the modern date and time API), explicit resource management (<code>using<\/code> declarations) and an Iterator <code>join<\/code>\/chunking family, expected in ES2027. Decorators, pattern matching and records and tuples are not finished, and the pipeline operator is still a proposal. Treat anything not on the finished list as something that may still change.<\/p>\n<h2>Which version should you target?<\/h2>\n<ul>\n<li><strong>Learning:<\/strong> learn ES2015 to ES2022 first. That covers nearly all code you will read: <code>let<\/code>\/<code>const<\/code>, arrow functions, modules, promises and async\/await, destructuring, <code>?.<\/code> and <code>??<\/code>, classes.<\/li>\n<li><strong>Browser apps:<\/strong> modern browsers update automatically, so ES2022-level syntax is generally safe. For newer library methods, check MDN or caniuse for the browsers your users have. Bundlers such as Vite, esbuild and Babel can compile newer syntax down, but they cannot add missing built-ins unless you include a polyfill.<\/li>\n<li><strong>Node.js:<\/strong> check your Node version, since each release ships a specific V8 version. Features like <code>Promise.try<\/code> and <code>RegExp.escape<\/code> were not in Node 22, for example (<code>Promise.try<\/code> throws &#8220;is not a function&#8221; on Node 22.22).<\/li>\n<li><strong>Interviews:<\/strong> use ES2015+ syntax freely; interviewers expect <code>const<\/code>, arrow functions, <code>Map<\/code>\/<code>Set<\/code>, and destructuring. If you want to practice them on real problems, <a href=\"https:\/\/app.algocademy.com\/\">AlgoCademy<\/a> has JavaScript lessons with step-by-step feedback.<\/li>\n<\/ul>\n<h2>Common mistakes<\/h2>\n<ul>\n<li><strong>Calling ES6 &#8220;the latest version&#8221;.<\/strong> It was released in 2015; the latest edition is ES2026.<\/li>\n<li><strong>Using a method that the runtime doesn&#8217;t have.<\/strong> Syntax errors show up at load time, but a missing method such as <code>Promise.try<\/code> fails only when the line runs.<\/li>\n<li><strong>Expecting <code>sort<\/code> and <code>reverse<\/code> to be pure.<\/strong> They mutate the array. Use <code>toSorted()<\/code> and <code>toReversed()<\/code> (ES2023) when you need a copy.<\/li>\n<li><strong>Using <code>||<\/code> for defaults.<\/strong> <code>0 || 42<\/code> is <code>42<\/code>; <code>0 ?? 42<\/code> is <code>0<\/code>.<\/li>\n<\/ul>\n","protected":false},"excerpt":{"rendered":"<p>Every ECMAScript edition from ES1 to ES2026 with release dates, what each added, and the ES5, ES6 and ES7 naming explained, with runnable examples.<\/p>\n","protected":false},"author":1,"featured_media":4143,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[23],"tags":[],"class_list":["post-4144","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-problem-solving"],"_links":{"self":[{"href":"https:\/\/algocademy.com\/blog\/wp-json\/wp\/v2\/posts\/4144"}],"collection":[{"href":"https:\/\/algocademy.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/algocademy.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/algocademy.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/algocademy.com\/blog\/wp-json\/wp\/v2\/comments?post=4144"}],"version-history":[{"count":1,"href":"https:\/\/algocademy.com\/blog\/wp-json\/wp\/v2\/posts\/4144\/revisions"}],"predecessor-version":[{"id":8940,"href":"https:\/\/algocademy.com\/blog\/wp-json\/wp\/v2\/posts\/4144\/revisions\/8940"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/algocademy.com\/blog\/wp-json\/wp\/v2\/media\/4143"}],"wp:attachment":[{"href":"https:\/\/algocademy.com\/blog\/wp-json\/wp\/v2\/media?parent=4144"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/algocademy.com\/blog\/wp-json\/wp\/v2\/categories?post=4144"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/algocademy.com\/blog\/wp-json\/wp\/v2\/tags?post=4144"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}