You can actually stop a “forEach” loop in JavaScript – in 5 ways

Can you break out of a “forEach” loop in JavaScript?

It’s an amazing question to challenge just how well you really know JavaScript.

Because we’re not talking for loops — or this would have been ridiculously easy: you just break:

But you wouldn’t dare do this with forEach, or disaster happens:

What about return… mhmm.

What do you think is going to happen here:

return should easily end the loop at 5 and take us to the outer log right?

Wrong:

Remember: forEach takes a callback and calls it FOR EACH item in the array.

JavaScript
// Something like this: Array.prototype.forEach = function (callback, thisCtx) { const length = this.length; let i = 0; while (i < length) { // ๐Ÿ‘‡ callback run once and only once callback.call(thisCtx, this[i], i, this); i++; } };

So return only ends the current callback call and iteration; doing absolutely nothing to stop the overall loop.

It’s like here; trying to end func2() from func1() — obviously not going to work:

5 terrible ways to stop a forEach loop

1. Throw an exception

You can stop any forEach loop by throwing an exception:

JavaScript
const nums = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; try { nums.forEach((num) => { if (num === 5) { throw new Error('just to stop a loop?'); } console.log(num); }); } catch { console.log('finally stopped!'); }

Of course we’re just having fun here — this would be horrible to see in real-world code. We only create exceptions for problems, not planned code like this.

2. process.exit()

This one is extreme:

JavaScript
const nums = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; nums.forEach((num) => { if (num === 5) { process.exit(0); } console.log(num); });

Not only are you ending the loop, you’re ending the entire program!

You won’t even get to the console.log() here:

3. Array some()

This is bad.

It works and some people recommended it for this; but this lowers readability as it’s clearly not what it’s meant for.

This is what it’s meant for:

JavaScript
const nums = ['bc', 'bz', 'ab', 'bd']; const hasStartingA = nums.some((num) => num.startsWith('a') ); const hasStartingP = nums.some((num) => num.startsWith('p') ); console.log(hasStartingA); // true console.log(hasStartingP); // false

4. Set array length to 0

But here’s something even more daring: Setting the length of the array to 0!

JavaScript
const nums = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; function myFunc() { nums.forEach((num) => { if (num === 5) { nums.length = 0; } console.log(num); }); console.log('it worked!'); console.log(nums); } myFunc();

Setting the array’s length completely destroys and resets it — EMPTY array:

5. Use Array splice()

Things get even weirder when you use Array splice() to stop the foreach loop, deleting slashing away elements mid-way!

JavaScript
const nums = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; function myFunc() { nums.forEach((num, i) => { if (num === 5) { nums.splice(i + 1, nums.length - i); } console.log(num); }); console.log('spliced away!'); console.log(nums); } myFunc();

3 great ways to stop a loop

1. Do you really need to break?

Instead of using the terrible methods above to stop a forEach loop…

Why not refactor your code so you don’t need to break at all?

So, instead of this:

JavaScript
const nums = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; try { nums.forEach((num) => { if (num === 5) { // โŒ break at 5 throw new Error('just to stop a loop?'); } console.log(num); }); } catch { console.log('finally stopped!'); }

We could have simply done this:

JavaScript
const nums = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; nums.forEach((num) => { if (num < 5) { // ignore 5 or more console.log(num); } }); console.log('no need to break!');

2. Use for of

But if you really want to jump out of the loop early then you’re much better of with the for..of loop:

JavaScript
const nums = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; try { for (const num of nums) { if (num === 5) { break; // ๐Ÿ‘ˆ break works } console.log(num); } } catch { console.log('finally stopped!'); }

3. Use traditional for

Or with the traditional for loop, for more fine-grained control:

JavaScript
const nums = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; try { for (let i = 0; i < nums.length; i += 2) { // ๐Ÿ‘ˆ jump by 2 const num = nums[i]; if (num === 5) { break; } console.log(num); } } catch { console.log('finally stopped!'); }

Final thoughts

So there are ways to “break” from a forEach loop, but they’re pretty messy and insane.

Instead try refactoring the code to avoid needing to break in the first place. Or switch to for and for..of for a cleaner, readable approach.

How to HACK JavaScript with Well-Known Symbols (5 ways)

They call them well-known symbols – even though most developers have never used them or even heard of them.

They’re a really cool feature you can use to make magic like this happen:

Use well-known symbols to magically redefine JavaScript's core functionalities to behave in unique and delightful ways.

You’ll see how we built the List class with a well-known symbol to do this.

They’re all about completely customizing the normal behavior of built-in operations like for..of. It’s like operator overloading in C++ and C#.

Also all static methods of the Symbol class.

1. Symbol.hasInstance

So first up we have Symbol.hasInstance: for easily changing how the instanceof operator behaves.

JavaScript
const person = new Person({ name: 'Tari Ibaba', at: 'codingbeautydev.com', }); // Because of Symbol.hasInstance console.log(person instanceof Person); // โŒ false (!!) console.log('Tari Ibaba' instanceof Person); // โœ… true console.log('Person' instanceof Person); // โŒ false

Normally instanceof is all about checking if a variable is an instance of class.

JavaScript
class Person { constructor({ name, at }) { this.name = name; this.at = at; } } const person = new Person({ name: 'Tari Ibaba', at: 'codingbeautydev.com', }); console.log(person instanceof Person); // โœ… true console.log('Person' instanceof Person); // โŒ false console.log(person instanceof String); // โŒ false

This is as it should be. Pretty standard stuff.

But with Symbol.hasInstance we can completely transform how instanceof works:

JavaScript
class Person { constructor({ name, at }) { this.name = name; this.at = at; } static [Symbol.hasInstance](obj) { const people = ['Tari Ibaba', 'Ronaldo']; return people.includes(obj); } }

Now a Person is no longer a Person, as far as instanceof is concerned.

JavaScript
const person = new Person({ name: 'Tari Ibaba', at: 'codingbeautydev.com', }); console.log(person instanceof Person); // โŒ false (!!) console.log('Tari Ibaba' instanceof Person); // โœ… true console.log('Person' instanceof Person); // โŒ false

What if we don’t want to completely override it, but instead extend it in an intuitive way?

We can’t use instanceof inside the symbol because that’ll quickly lead to an infinite recursion:

Instead we compare the special constructor property of the object to our own:

JavaScript
class Fruit { constructor(name) { this.name = name; } [Symbol.hasInstance](obj) { const fruits = ['๐Ÿ', '๐ŸŒ', '๐Ÿ‰', '๐Ÿ‡']; // this == this.constructor in a static method return obj.constructor === this || fruits.includes(obj); } } const fruit = new Fruit('apple');

If you’re just hearing of .constructor, this should explain everything:

JavaScript
String.prototype.constructor.prototype.constructor === String // true

2. Symbol.iterator

Our next hack is Symbol.iterator, for totally altering how and if loop works on an object.

Remember this:

We did this thanks to Symbol.iterator:

JavaScript
class List { elements = []; wordEmojiMap = { red: '๐Ÿ”ด', blue: '๐Ÿ”ต', green: '๐ŸŸข', yellow: '๐ŸŸก', }; add(element) { this.elements.push(element); return this; } // Generator *[Symbol.iterator]() { for (const element of this.elements) { yield this.wordEmojiMap[element] ?? element; } } }

We see generators crop up once again.

Any time we use for..of

JavaScript
const numbers = [1, 2, 3]; for (const num of numbers) { console.log(num); } /* 1 2 3 */

This happens behind the scenes:

JavaScript
const iterator = numbers[Symbol.iterator](); // for..of: Keep calling .next() and using value until done console.log(iterator.next()); // Object {value: 1, done: false} console.log(iterator.next()); // Object {value: 2, done: false} console.log(iterator.next()); // Object {value: 3, done: false} console.log(iterator.next()); // Object {value: undefined, done: true}

So with Symbol.iterator we completely changed what for..of does with any List object:

JavaScript
class List { // ... *[Symbol.iterator]() { for (const element of this.elements) { yield this.wordEmojiMap[element] ?? element; } } }
JavaScript
const colors = new List(); colors.add('red').add('blue').add('yellow'); const iterator = colors[Symbol.iterator](); console.log(iterator.next()); // { value: '๐Ÿ”ด', done: false } console.log(iterator.next()); // { value: '๐Ÿ”ต', done: false } console.log(iterator.next()); // { value: '๐ŸŸก', done: false } console.log(iterator.next()); // { value: undefined, done: true }

4. Symbol.toPrimitive

With Symbol.toPrimitive we quickly go from this:

To this:

We did this by overriding Symbol.toPrimitive:

JavaScript
class Person { constructor({ name, at, favColor }) { this.name = name; this.at = at; this.favColor = favColor; } [Symbol.toPrimitive]() { return `I'm ${this.name}`; } }

Now we can use a Person object anywhere we use a string for interpolation & concatenation:

JavaScript
const str = 'Person: ' + person; console.log(str); // Person: I'm Tari Ibaba

There’s even a hint parameter that makes an object act like a number, string, or something else.

JavaScript
class Money { constructor(amount, currency) { this.amount = amount; this.currency = currency; } [Symbol.toPrimitive](hint) { if (hint === 'string') { return `${this.amount} ${this.currency}`; } else if (hint === 'number') { return this.amount; } else if (hint === 'default') { return `${this.amount} ${this.currency}`; } } } const price = new Money(500, 'USD'); console.log(String(price)); // 500 USD console.log(+price); // 500 console.log('Price is ' + price); // Price is 500 USD

4. Symbol.split

Genius well-known symbol for turning your custom objects into string separators:

JavaScript
class Greedy { [Symbol.split](str) { return `Me: ${str}, you: 0`; } } class ReadableNumber { [Symbol.split](str) { return str.split('').reduceRight((acc, cur, index, arr) => { return index % 3 === 0 && index < arr.length - 1 ? cur + ',' + acc : cur + acc; }, ''); } } console.log('1-000-000'.split('-')); // [ '1', '000', '000' ] console.log('1000000'.split(new Greedy())); // Me: 1000000, you: 0 console.log('1000000'.split(new ReadableNumber())); // 1,000,000

5. Symbol.search

Just like Symbol.split, transform your custom objects into sophisticated string searching tools:

JavaScript
class Topic { static topics = { 'codingbeautydev.com': ['JavaScript', 'VS Code', 'AI'], }; constructor(value) { this.value = value; } [Symbol.search](where) { const topic = this.constructor.topics[where]; if (!topic) return -1; return topic.indexOf(this.value); } } const str = 'codingbeautydev.com'; console.log(str.search(new Topic('VS Code'))); // 1 console.log(str.search(new Topic('Economics'))); // -1

Final thoughts

From looping to splitting to searching, well-known symbols let us redefine our core functionalities to behave in unique and delightful ways, pushing the boundaries of what’s possibly in JavaScript.

10 amazing web development tools you need to know (#2)

10 more amazing web dev tools to boost your workflow and make development more enjoyable.

From stunning animations to rapid API creation & documentation, these tools will help you get things done faster than ever.

1. Heat.js

Create stunning heat maps and charts with this incredible UI library.

Like this:

GitHub-like heatmap.

Very similar to my GitHub profile (but with way more greens of course):

No external libraries needed, it’s dependency-free.

Customize every detail with a wide range of settings, or choose from over 10 pre-made themes in 40+ languages. Get ready to bring your data to life.

2. Postman

Simply the best for creating and testing APIs.

If you still use curl then you must living in the stone age. Or maybe you have some sort of CLI superiority complex.

Just making a simple POST request is pain; Stressful editing, strict formatting requirements that don’t even stay consistent with the OS and terminal.

1st one works on Linux, 2nd on Windows CMD (I guess), 3rd on Powershell…๐Ÿ˜ด

Why go through any of that when you have a nice and easy GUI with none of these problems?

Body data is easy, query params are easy.

It even has built-in support for testing APIs from Paypal and Google.

3. React Toastify

By far the easiest way I found to add toast notifications to your React app.

All you need is this simple code:

JavaScript
import React from 'react'; import { ToastContainer, toast } from 'react-toastify'; import 'react-toastify/dist/ReactToastify.css'; function App(){ const notify = () => toast("Wow so easy!"); return ( <div> <button onClick={notify}>Notify!</button> <ToastContainer /> </div> ); }

Look it even has all sorts of colorful progress bars to show how much time left before it goes away ๐Ÿ‘‡

And dark mode of course. And different themes for different purposes.

4. GitLens for VS Code

VS Code source control on steroids.

Packed full with tons of views with essential repo data and current file info: file history, commits, branches, remotes, and more.

Even history of particular line in the file, with valuable related data and actions.

5. Palettify

Craft stunning UI color schemes in real-time with Palettify.

That H1 font is awesome by the way.

Play around with shadcn-ui components, toggle light/dark mode, then grab the the CSS theme code with a single click.

Several themes to choose from:

Wiza theme:

6. LinkPreview

I use this to easily get a preview image from a URL.

Like Notion does for their Bookmark block:

Better than depending on unreliable web scraping libraries, or rigid 3rd-party Link Preview UI components.

You just make an API request to this URL:

Plain text
https://api.linkpreview.net/?q=your_url_to_preview

And you easily get your image, along with relevant information for preview.

If we go to the image URL we’ll see it’s the same exact one Notion showed:

7. Lottie

Breathe life into your apps with Lottie.

Lottie takes the magic from Adobe After Effects and brings it straight to your mobile and web apps.

Imagine the experience needed to create this ๐Ÿ‘‡ Then imagine recreating it with raw CSS.

So no more hand-coding โ€“ Lottie uses a special plugin to turn those After Effects animations into a lightweight JSON format that works flawlessly on any device.

8. Free Icons

Unleash your creativity with a treasure trove of over 22,000 icons!

Dive into a world of beautiful and free SVG icons, all meticulously tagged and ready to be discovered at your fingertips.

Including dinosaurs like Internet Explorer.

Simply type in a keyword and browse through countless icons to find the perfect visual match for your project.

9. jwt.io

I use this often when having nasty bugs with JSON web token from Firebase Auth or some stuff.

You just paste your token on the left and you instantly see the decoded value on the right.

And using this got me confused at first; I thought JWT was like an encryption where only the receiver could know what was there with the secret?

But no; it turned out JWT is for verifying the source of the message, not hiding information. Like in crypto wallets and transactions private and public keys.

10. TypeSpec

Use this to stop wasting time manually documenting your API for public use.

Describe your data once, and TypeScript magically conjures up everything you need: schema, API specifications, client / server code, docs, and more.

Final thoughts

Use these awesome tools to upgrade your productivity and make development more fun.

Why coding is power

Coding is the art of bringing thoughts into digital and physical reality.

I love this definition; so many gems to unpack from 1 concise statement.

With coding we wield the power to shape the digital realm according to our imagination and needs.

Picture this: You’re sitting in front of a blank canvas, armed with nothing but your thoughts and a bunch of dev tools.

With a few lines of code, you summon objects into existence, breathe life into algorithms, and orchestrate systems that operate with precision and purpose.

In this digital realm you are the architect, the creator, the god of your own universe. Every line of code is a brushstroke, painting the landscape of your creation.

You design the story of execution, from the inception of an idea to its realization, with the potent aid of lightning-fast computers that transform your commands into tangible results.

Coding is a force amplifier.

Thanks to iteration, recursion, and more, tiny scripts can easily complete unbelievable amounts of work in fractions of a second; work that no one on their own could possibly hope to finish in 10 life times.

Or operating continuously, 24/7, even when everyone is asleep, and throughout all seasons and holidays.

You breathe life into those inert lines of text, imbuing them with functionality and purpose. Bringing forth programs and applications that serve myriad functions; from simplifying mundane tasks to revolutionizing entire industries.

The mere challenge of carefully constructing a complex software system from the growing gives you a strong sense of self-accomplishment, achievement and inner power.

Even going beyond ourselves, coding is a gateway to solving real-world problems that surround us. From streamlining business operations to revolutionizing healthcare, coding empowers us to tackle challenges with efficiency and innovation.

Coding enables us to innovate continually, pushing the boundaries of what is possible and driving progress forward. Whether it’s developing groundbreaking applications, pioneering new technologies, or optimizing existing systems, coding empowers us to make an impact on a global scale.

Consider the countless innovations that have reshaped our world: from social media to e-commerce to the AI tools many of us have gone crazy about; each breakthrough stems from the minds of individuals who dared to dream and had the skills to code their visions into reality.

Apps that never once existed; now brought into existence with the power of thought; now installed in the devices of billions across all the nations of the globe; now influencing lives every single day.

In our information age being able to code is like possessing a superpowerโ€”an ability to wield technology as a force for good, to shape the world in ways previously unimaginable. With coding we have the power to improve the quality of life for billions, to democratize access to information and opportunity, and to pave the way for a brighter, more connected future.

Coding is more than just a technical skillโ€”it is a catalyst for change, a tool for empowerment, and a gateway to infinite possibilities. With it we can shape a future that’s not only technologically advanced but also as close to utopia as we can get.

Simply put: Coding a better world into existence for all, enjoying every step along the way.

Stop using nested ifs: Do this instead

Typical use case for nested ifs: you want to perform all sorts of checks on some data to make sure it’s valid before finally doing something useful with it.

Donโ€™t do this! ๐Ÿ‘‡

JavaScript
function sendMoney(account, amount) { if (account.balance > amount) { if (amount > 0) { if (account.sender === 'user-token') { account.balance -= amount; console.log('Transfer completed'); } else { console.log('Forbidden user'); } } else { console.log('Invalid transfer amount'); } } else { console.log('Insufficient funds'); } }

Thereโ€™s a better way:

JavaScript
function sendMoney(account, amount) { if (account.balance < amount) { console.log('Insufficient funds'); return; } if (amount <= 0) { console.log('Invalid transfer amount'); return; } if (account.sender !== 'user-token') { console.log('Forbidden user'); return; } account.balance -= amount; console.log('Transfer completed'); }

See how much cleaner it is? Instead of nesting ifs, we have multiple if statements that do a check and return immediately if the condition wasn’t met. In this pattern, we can call each of the if statements a guard clause.

If you do a lot of Node.js, you’ve probably seen this flow in Express middleware:

JavaScript
function authMiddleware(req, res, next) { const authToken = req.headers.authorization; if (!authToken) { return res.status(401).json({ error: 'Unauthorized' }); } if (authToken !== 'secret-token') { return res.status(401).json({ error: 'Invalid token' }); } if (req.query.admin === 'true') { req.isAdmin = true; } next(); }

Itโ€™s much better than this, right? :

JavaScript
function authMiddleware(req, res, next) => { const authToken = req.headers.authorization; if (authToken) { if (authToken === 'secret-token') { if (req.query.admin === 'true') { req.isAdmin = true; } return next(); } else { return res.status(401).json({ error: 'Invalid token' }); } } else { return res.status(401).json({ error: 'Unauthorized' }); } };

You never go beyond one level of nesting. We can avoid the mess that we see in callback hell.

How to convert nested ifs to guard clauses

The logic for this for doing this is simple:

1. Find the innermost/success if

Here we can clearly see it’s the cond3 if. After this, if we don’t do any more checks and take the action we’ve always wanted to take.

JavaScript
function func(cond1, cond2, cond3) { if (cond1) { if (cond2) { if (cond3) { console.log('PASSED!'); console.log('taking success action...'); } else { console.log('failed condition 3'); } } else { console.log('failed condition 2'); } } else { console.log('failed condition 1'); } }

2. Invert the outermost if and return

Negate the if condition to put the else statements’ body in there and add a return after.

Delete the else braces (keep the body, it still contains the formerly nested ifs, and move the closing if brace to just after the return.

So:

JavaScript
function func(cond1, cond2, cond3) { if (!cond1) { // ๐Ÿ‘ˆ inverted if condition // ๐Ÿ‘‡ body of former else clause console.log('failed condition 1'); return; // ๐Ÿ‘ˆ exit on fail } // ๐Ÿ‘‡ remaining nested ifs to convert to guard clauses if (cond2) { if (cond3) { console.log('PASSED!'); console.log('taking success action...'); } else { console.log('failed condition 3'); } } else { console.log('failed condition 2'); } }

3. Do the same for each nested if until you reach the success if

And then:

JavaScript
function func(cond1, cond2, cond3) { if (!cond1) { console.log('failed condition 1'); return; } if (!cond2) { console.log('failed condition 2'); return; } // ๐Ÿ‘‡ remaining nested ifs to convert if (cond3) { console.log('PASSED!'); console.log('taking success action...'); } else { console.log('failed condition 3'); } }

And finally:

JavaScript
function func(cond1, cond2, cond3) { if (!cond1) { console.log('failed condition 1'); return; } if (!cond2) { console.log('failed condition 2'); return; } if (!cond3) { console.log('failed condition 3'); return; } console.log('PASSED!'); console.log('taking success action...'); }

I use the JavaScript Booster extension to make inverting if statements in VS Code much easier.

Here we only had to put the cursor in the if keyword and activate the Show Code Actions command (Ctrl + . by default).

Check out this article for an awesome list of VSCode extensions you should definitely install alongside with JavaScript Booster.

Tip: Split guard clauses into multiple functions and always avoid if/else

What if we want to do something other after checking the data in an if/else? For instance:

JavaScript
function func(cond1, cond2) { if (cond1) { if (cond2) { console.log('PASSED!'); console.log('taking success action...'); } else { console.log('failed condition 2'); } console.log('after cond2 check'); } else { console.log('failed condition 1'); } console.log('after cond1 check'); }

In this function regardless of cond1‘s value, the 'after cond1 check' the line will still print. Similar thing for the cond2 value if cond1 is true.

In this case, it takes a bit more work to use guard clauses:

If we try to use guard clauses, we’ll end up repeating the lines that come after the if/else checks:

JavaScript
function func(cond1, cond2) { if (!cond1) { console.log('failed condition 1'); console.log('after cond1 check'); return; } if (!cond2) { console.log('failed condition 2'); console.log('after cond2 check'); console.log('after cond1 check'); return; } console.log('PASSED!'); console.log('taking success action...'); console.log('after cond2 check'); console.log('after cond1 check'); } func(true);

Because the lines must be printed, we print them in the guard clause before returning. And then, we print it in all(!) the following guard clauses. And once again, in the main function body if all the guard clauses were passed.

So what can we do about this? How can we use guard clauses and still stick to the DRY principle?

Well, we split the logic into multiple functions:

JavaScript
function func(cond1, cond2) { checkCond1(cond1, cond2); console.log('after cond1 check'); } function checkCond1(cond1, cond2) { if (!cond1) { console.log('failed condition 1'); return; } checkCond2(cond2); console.log('after cond2 check'); } function checkCond2(cond2) { if (!cond2) { console.log('failed condition 2'); return; } console.log('PASSED!'); console.log('taking success action...'); }

Let’s apply this to the Express middleware we saw earlier:

JavaScript
function authMiddleware(req, res, next) { checkAuthValidTokenAdmin(req, res, next); } function checkAuthValidTokenAdmin(req, res, next) { const authToken = req.headers.authorization; if (!authToken) { return res.status(401).json({ error: 'Unauthorized' }); } checkValidTokenAdmin(req, res, next); } function checkValidTokenAdmin(req, res, next) { const authToken = req.headers.authorization; if (authToken !== 'secret-token') { return res.status(401).json({ error: 'Invalid token' }); } checkAdmin(req, res, next); } function checkAdmin(req, res, next) { if (req.query.admin === 'true') { req.isAdmin = true; } next(); }

In a way, we’ve replaced the if/else statements with a chain of responsibility pattern. Of course, this might be an overkill for simple logic like a basic Express request middleware, but the advantage here is that it delegates each additional check to a separate function, separating responsibilities and preventing excess nesting.

Key takeaways

Using nested ifs in code often leads to complex and hard-to-maintain code; Instead, we can use guard clauses to make our code more readable and linear.

We can apply guard clauses to different scenarios and split them into multiple functions to avoid repetition and split responsibilities. By adopting this pattern, we end up writing cleaner and more maintainable code.

The 5 most transformative JavaScript features from ES14

JavaScript has come a long way in the past 10 years with brand new feature upgrades in each one.

Still remember when we created classes like this?

JavaScript
function Car(make, model) { this.make = make; this.model = model; } // And had to join strings like this Car.prototype.drive = function() { console.log("Vroom! This " + this.make + " " + this.model + " is driving!"); };

Yeah, a lot has changed!

Let’s take a look at the 5 most significant features that arrived in ES14 (2023); and see the ones you missed.

1. toSorted()

Sweet syntactic sugar.

ES14’s toSorted() method made it easier to sort an array and return a copy without mutation.

Instead of this:

JavaScript
const nums = [5, 2, 6, 3, 1, 7, 4]; const sorted = clone.sort(); console.log(sorted); // [1, 2, 3, 4, 5, 6, 7] // โŒโŒ Mutated console.log(nums); // [1, 2, 3, 4, 5, 6, 7]

We now got to do this โœ…:

JavaScript
const nums = [5, 2, 6, 3, 1, 7, 4]; // โœ… toSorted() prevents mutation const sorted = nums.toSorted(); console.log(sorted); // [1, 2, 3, 4, 5, 6, 7] console.log(nums); // [5, 2, 6, 3, 1, 7, 4]

toSorted() takes a callback for controlling sorting behavior – ascending or descending, alphabetical or numeric. Just like sort().

2. Array find from last

Searching from the first item isn’t always ideal:

JavaScript
const tasks = [ { date: '2017-03-05', name: '๐Ÿ‘Ÿrun a 5k' }, { date: '2017-03-04', name: '๐Ÿ‹๏ธlift 100kg' }, { date: '2017-03-04', name: '๐ŸŽถwrite a song' }, // 10 million records... { date: '2024-04-24', name: '๐Ÿ›๏ธfinally sleep on time' }, { date: '2024-04-24', name: '๐Ÿ“1h writing with no breaks' }, ]; const found = tasks.find((item) => item.date === '2024-03-25'); const foundIndex = tasks.findIndex((item) => item.date === '2024-03-25'); console.log(found); // { value: '2024-03-25', name: 'do 1000 pushups' } console.log(foundIndex); // 9,874,910

You can easily see that it’ll be much faster for me to search our gigantic list from the end instead of start.

JavaScript
const tasks = [ { date: '2017-03-05', name: 'run a 5k' }, { date: '2017-03-04', name: 'lift 100kg' }, { date: '2017-03-04', name: 'write a song' }, // 10 million records... { date: '2024-04-24', name: 'finally sleep on time' }, { date: '2024-04-24', name: '1h writing with no breaks' }, ]; // โœ… Much faster const found = tasks.findLast((item) => item.date === '2024-03-25'); const foundIndex = tasks.findLastIndex((item) => item.date === '2024-03-25'); console.log(found); // { value: '2024-03-25', name: 'do 1000 pushups' } console.log(foundIndex); // 9,874,910

And they’re also times you MUST search from the end for your program work.

Like we want to find the last even number in a list of numbers, find and findIndex will be incredibly off.

JavaScript
const nums = [7, 14, 3, 8, 10, 9]; // โŒ gives 14, instead of 10 const lastEven = nums.find((value) => value % 2 === 0); // โŒ gives 1, instead of 4 const lastEvenIndex = nums.findIndex((value) => value % 2 === 0); console.log(lastEven); // 14 console.log(lastEvenIndex); // 1

And calling reverse() won’t work either, even as slow as it would be:

JavaScript
const nums = [7, 14, 3, 8, 10, 9]; // โŒ Copying the entire array with the spread syntax before // calling reverse() const reversed = [...nums].reverse(); // correctly gives 10 const lastEven = reversed.find((value) => value % 2 === 0); // โŒ gives 1, instead of 4 const reversedIndex = reversed.findIndex((value) => value % 2 === 0); // Need to re-calculate to get original index const lastEvenIndex = reversed.length - 1 - reversedIndex; console.log(lastEven); // 10 console.log(reversedIndex); // 1 console.log(lastEvenIndex); // 4

So in cases like where the findLast() and findLastIndex() methods come in handy.

JavaScript
const nums = [7, 14, 3, 8, 10, 9]; // โœ… Search from end const lastEven = nums.findLast((num) => num % 2 === 0); // โœ… Maintain proper indexes const lastEvenIndex = nums.findLastIndex((num) => num % 2 === 0); console.log(lastEven); // 10 console.log(lastEvenIndex); // 4

This code is shorter and more readable. Most importantly, it produces the correct result.

3. toReversed()

Another new Array method to promote immutability and functional programming.

Before – with reverse() โŒ:

JavaScript
const arr = [5, 4, 3, 2, 1]; const reversed = arr.reverse(); console.log(reversed); // [1, 2, 3, 4, 5] // โŒ Original modified console.log(arr); // [1, 2, 3, 4, 5]

Now – with toReversed() โœ…:

JavaScript
const arr = [5, 4, 3, 2, 1]; const reversed = arr.toReversed(); console.log(reversed); // [1, 2, 3, 4, 5] // โœ… No modification console.log(arr); // [5, 4, 3, 2, 1]

I find these immutable methods awesome for constantly chaining methods over and over without worrying about the original variables:

JavaScript
// โœ… Results are independent of each other const nums = [5, 2, 6, 3, 1, 7, 4]; const result = nums .toSorted() .toReversed() .map((n) => n * 2) .join(); console.log(result); // 14,12,10,8,6,4,2 const result2 = nums .map((n) => 1 / n) .toSorted() .map((n) => n.toFixed(2)) .toReversed(); console.log(result2); // [ '1.00', '0.50', '0.33', '0.25', // '0.20', '0.17', '0.14' ]

4. toSpliced()

Lovers of functional programming will no doubt be pleased with all these new Array methods.

This is the immutable counterpart of .splice():

JavaScript
const colors = ['red๐Ÿ”ด', 'purple๐ŸŸฃ', 'orange๐ŸŸ ', 'yellow๐ŸŸก']; // Remove 2 items from index 1 and replace with 2 new items const spliced = colors.toSpliced(1, 2, 'blue๐Ÿ”ต', 'green๐ŸŸข'); console.log(spliced); // [ 'red๐Ÿ”ด', 'blue๐Ÿ”ต', 'green๐ŸŸข', 'yellow๐ŸŸก' ] // Original not modified console.log(colors); // ['red๐Ÿ”ด', 'purple๐ŸŸฃ', 'orange๐ŸŸ ', 'yellow๐ŸŸก'];

5. Array with() method

with() is our way of quickly change an array element with no mutation whatsoever.

Instead of this usual way:

JavaScript
const arr = [5, 4, 7, 2, 1] // Mutates array to change element arr[2] = 3; console.log(arr); // [5, 4, 3, 2, 1]

ES14 now let us do this:

JavaScript
const arr = [5, 4, 7, 2, 1]; const replaced = arr.with(2, 3); console.log(replaced); // [5, 4, 3, 2, 1] // Original not modified console.log(arr); // [5, 4, 7, 2, 1]

Final thoughts

They were other features but ES14 was all about easier functional programming and built-in immutability.

With the rise of React we’ve seen declarative JavaScript explode in popularity; it’s only natural that more of them come baked into the language as sweet syntactic sugar.

5 unnecessary VS Code extensions you should uninstall now

Can you count how many VS Code extensions you have right now?

Me: A whooping 56.

If you’re finding VS Code getting slower and more power-hungry with time, this number could well be the reason.

Because EVERY new extension added increases the app’s memory and CPU usage.

Coding is already challenging enough; Nobody need contend with this:

Maybe I should just give up music instead.

So we need to keep this number as low as possible to minimize this resource usage; ad also stopping these extensions from clashing with one another or with native functionality.

And you know, there’s a significant number of extensions in the Marketplace that provide functionality VSCode already has built-in.

Usually they were made when the feature wasn’t added yet; but once that happened they became largely redundant additions.

So below, I cover a list of these integrated VSCode features and extensions that provide them. Uninstalling these now dispensable extensions will increase your editor’s performance and efficiency.

I’ll be listing settings that control the behavior of these features. If you don’t know how to change settings, this guide will help.

Related: 10 Must-Have VSCode Extensions for Web Development

1. Auto closing of HTML tags

When you add a new HTML tag, this feature automatically adds the corresponding closing tag.

The closing tag for the div is automatically added.
The closing tag for the div is automatically added.

Extensions for this

These extensions add the auto-closing feature to VSCode:

  • Auto Close Tag (12.3M+ downloads): “Automatically add HTML/XML close tag, same as Visual Studio IDE or Sublime Text”.
  • Close HTML/XML Tag (344K downloads): “Quickly close last opened HTML/XML tag”.

But feature already built in

I use these settings to enable/disable the auto-closing of tags in VSCode:

  • HTML: Auto Closing Tags: “Enable/disable autoclosing of HTML tags”. It is true by default.
  • JavaScript: Auto Closing Tags: “Enable/disable automatic closing of JSX tags”. It is true by default.
  • TypeScript: Auto Closing Tags: “Enable/disable automatic closing of JSX tags”. It is true by default.
Settings for auto closing in the VSCode Settings UI.
Settings for auto-closing in the Settings UI.

Add the following to your settings.json file to turn them on:

settings.json

{
  "html.autoClosingTags": true,
  "javascript.autoClosingTags": true,
  "typescript.autoClosingTags": true
}

2. Path autocompletion

The path autocompletion feature provides a list of files in your project to choose from when importing a module or linking a resource in HTML.

Extensions for this

These extensions add the path autocompletion feature to VSCode:

  1. Path IntelliSense (12.5M+ downloads): “Visual Studio Code Plugin that autocompletes filenames”.
  2. Path Autocomplete (1.7M+ downloads): “Provides path completion for Visual Studio Code and VS Code for the web”.

But feature already built in

VS Code already has native path autocompletion.

When I type in a filename to import (typically when the opening quote is typed), a list suggested project files shows up for me to quickly choose from.

3. Snippets for HTML and CSS

These extensions help you save time by adding common HTML and CSS snippets using abbreviations you can easily recall.

Extensions for this

These extensions bring convenient HTML and/or CSS snippets to VSCode:

  • HTML Snippets (10.1M+ downloads): “Full HTML tags including HTML5 snippets”.
  • HTML Boilerplate (3.2M+ downloads): “A basic HTML5 boilerplate snippet generator”.
  • CSS Snippets (225K+ downloads): “Shorthand snippets for CSS”.

But feature already built-in

Emmet is a built-in VSCode feature that provides HTML and CSS snippets like these extensions. As you’ll see in the official VSCode Emmet guide, it’s enabled by default in html, haml, pug, slim, jsx, xml, xsl, css, scss, sass, less, and stylus files.

Comprehensive to say the least.

When you start typing an Emmet abbreviation, a suggestion will pop up with auto-completion options; You’ll also see a preview of the expansion as you type in the VSCode’s suggestion documentation fly-out (if it is open).

Using Emmet in VSCode.
Using Emmet in VSCode.

As you saw in the demo, this:

ol>li*3>p.rule$

turns into this:

<ol>
  <li>
    <p class="rule1">r</p>
  </li>
  <li>
    <p class="rule2"></p>
  </li>
  <li>
    <p class="rule3"></p>
  </li>
</ol>

Notice how similar the abbreviations are to CSS selectors. It’s by design; as stated on the official website, Emmet syntax is inspired by CSS selectors.

4. Bracket pair colorization

Bracket pair coloring is a popular syntax highlighting feature that colors brackets differently based on their order.

It makes it easier to identify scope and helps in writing expressions that involve many parentheses, such as single-statement function composition.

Extensions for this

Until VSCode had it built-in, these extensions helped enable the feature in the editor:

  1. Bracket Pair Colorizer 2 (6.1M+ downloads): “A customizable extension for colorizing matching brackets”. It has now been deprecated.
  2. Rainbow Brackets: (1.9M downloads): “A rainbow brackets extension for VS Code”.

I noticed Colorizer 2 has actually been deprecated since 2021 — wasn’t enough to stop millions from installing it every single year till date.

But feature already built in

After seeing the demand for bracket pair coloring and the performance issues involved in adding the feature as an extension, the VSCode team decided to integrate it into the editor.

In this blog, they say that the native bracket pair coloring feature is more than 10,000 times faster than Bracket Pair Colorizer 2.

Here’s the setting to enable/disable bracket pair colorization.

  • Editor > Bracket Pair Colorization: “Controls whether bracket pair colorization is enabled or not”. It is true by default, there’s been some debate about whether this should be the case here.
The bracket pair colorization option in the VSCode Settings UI.
The bracket pair colorization option in the Settings UI.

You can enable this by adding the following to your settings.json

settings.json

{
  "editor.bracketPairColorization.enabled": true
}

There is a maximum of 6 colors that can be used for successive nesting levels. Although each theme will have its maximum. For example, the Dracula theme has 6 colors by default, but the One Dark Pro theme has only 3.

Left: bracket pair colors in One Dark Pro theme. Right: bracket pair in Dracula theme.
Left: bracket pair colors in One Dark Pro theme. Right: bracket pair in Dracula theme.

Nevertheless, you can customize the bracket colors for any theme with the workbench.colorCustomizations setting.

  "workbench.colorCustomizations": {
    "[One Dark Pro]": {
      "editorBracketHighlight.foreground1": "#e78009",
      "editorBracketHighlight.foreground2": "#22990a",
      "editorBracketHighlight.foreground3": "#1411c4",
      "editorBracketHighlight.foreground4": "#ddcf11",
      "editorBracketHighlight.foreground5": "#9c15c5",
      "editorBracketHighlight.foreground6": "#ffffff",
      "editorBracketHighlight.unexpectedBracket.foreground": "#FF2C6D"
    }
  },

We specify the name of the theme in square brackets ([ ]), then we assign values to the relevant properties. The editorBracketHighlight.foregroundN property sets the color of the Nth set of brackets, and 6 is the maximum.

Now this will be the bracket pair colorization for One Dark Pro:

Customized bracket pair colorization for One Dark Pro theme.
Customized bracket pair colorization for One Dark Pro theme.

5. Auto importing of modules

With an auto-importing feature, when a function, variable, or some other member of a module is referenced in a file, the module is automatically imported into the file, saving time and effort.

The function is automatically imported from the file when referenced.
The function is automatically imported from the file when referenced.

If the module files are moved, the feature will help automatically update them.

Imports for a file are automatically updated on move.
Imports for a file are automatically updated on move.

Extensions for this

Here are some of the most popular extensions providing the feature for VSCode users:

  • Auto Import (3.8M downloads): “Automatically finds, parses, and provides code actions and code completion for all available imports. Works with Typescript and TSX”.
  • Move TS (810K downloads): “extension for moving typescript files and folders and updating relative imports in your workspace”.

But feature already built in

You can enable or disable auto-importing modules in VSCode with the following settings.

  • JavaScript > Suggest: Auto Imports: “Enable/disable auto import suggestions”. It is true by default.
  • TypeScript > Suggest: Auto Imports: “Enable/disable auto import suggestions”. It is true by default.
  • JavaScript > Update Imports on File Move: “Enable/disable automatic updating of import paths when you rename or move a file in VS Code”. The default value is prompt, meaning that a dialog is shown to you, asking if you want to update the imports of the moved file. Setting it to alwayswill cause the dialog to be skipped, and never will turn off the feature entirely.
  • TypeScript > Update Imports on File Move: “Enable/disable automatic updating of import paths when you rename or move a file in VS Code”. Like the previous setting, it has possible values of prompt, always, and never, and the default is prompt.
One of the auto import settings in the Settings UI.
One of the auto import settings in the Settings UI.

You can control these settings with these settings.json properties:

{
  "javascript.suggest.autoImports": true,
  "typescript.suggest.autoImports": true,
  "javascript.updateImportsOnFileMove.enabled": "prompt",
  "typescript.updateImportsOnFileMove.enabled": "prompt"
}

You can also add this setting if you want your imports to be organized any time the file is saved.

"editor.codeActionsOnSave": {
    "source.organizeImports": true
}

This will remove unused import statements and arrange import statements with absolute paths on top, providing a hands-off way to clean up your code.

Final thoughts

These extensions might have served a crucial purpose in the past, but not anymore for the most part, as much of the functionality they provide have been added as built-in VSCode features. Remove them to reduce the bloat and increase the efficiency of Visual Studio Code.

Why Devin AI can’t take your job.

Devin AI.

They claim itโ€™s the silver bullet for all software creation, a miraculous tech outperforming every other AI model and handling real-world programming with ease.

With recent news of Nvidia CEO, Jensen Huang, confidently predicting the impending death of coding, surely this Devin AI thing must be the first nail to go in the coffin.

Mhmm.

Sounds suspiciously familiar… AutoGPT, anyone? GPT Engineer? LOL.

Oh no… before jumping on the bandwagon we need to take a closer look at the deception behind this supposed game-changer.

The first glaring issue is the lack of transparency surrounding Devin AI’s performance metrics.

Sure, they claim it’s superior, but how did they arrive at these numbers? And where’s the proof? There’s a conspicuous absence of generated source code to back up their claims.

Without this crucial evidence you can’t take their word at face value.

Can Devin AI really make a meaningful impact in a real-world repository? Doubtful. And what about limitations? Not a word. It’s as if they want us to believe Devin AI is flawless, without a single drawback.

The demos provided by Devin AI are suspect at best; They showcase its abilities but conveniently omit crucial details.
Ever notice how they never revealed the prompts inputted by the user?

If you pause the videos and examine the timestamps, youโ€™ll find it takes hours, not the mere five minutes they lead you to believe. Itโ€™s a smoke and mirrors act designed to dazzle without substance.

And what about the demos themselves? Theyโ€™re basic, rudimentary at best. Many of the problems showcased are nothing more than following a tutorial, some of which even included code snippets.

Hype over competence.

Perhaps the most concerning aspect is the lack of public testing. If Devin AI truly lives up to the hype, why not let the public put it through its paces?

The reluctance to release it for testing raises red flags and hints at a possible cash grab scheme. Business may well soon find themselves disillusioned with promises that fail to materialize.

Trusting AI blindly is a path to failure.

Even if Devin AI does possess remarkable capabilities, it’s important to remember that code still requires human understanding and review to be acceptable. Software engineering is a nuanced field with countless variables; How can an AI know it is correct when its idea of correctness is bound by its training data?

If you think AI can replace developers so easily, then you probably missing the whole point of why we code. Coding at it’s core, is not about typing and compiling. It’s not even about creating apps or websites.

Coding is about specifying the requirements of a system with zero ambiguity. It’s about expressing the solution to a problem with absolute precision.

When you type in a prompt to ChatGPT with all the vivid descriptions and (hopefully) expressive constraints, you are coding.

The difference now is the glaring ambiguity of natural language; the lack of certainty of getting exactly what you want from the AI 100% of the time. Thatโ€™s why you can refine a prompt dozens of times and have absolutely nothing to show for it.

So AI can only be as good at generating code as the instructions it’s given. And describing the software you want with precision has always been the greatest challenge in software development.

If Devin AI can compel users to provide enough definitions, then perhaps it has potential. But until then, it remains an overhyped tool with limited utility.

AI’s role in programming is similar to the evolution of programming languages. As languages have progressed, programming has become more accessible. But has this led to fewer programmers? No. Instead, it has expanded the reach of programming, leading to more innovation and productivity.

Likewise AI-supported coding will enhance productivity, not replace developers. These AI models are essentially sophisticated search engines trained on vast amounts of data. They excel at common tasks but falter when faced with specific or innovative challenges. They lack the creativity and problem-solving abilities inherent in human developers.

Once again let’s not forget about reliability; AI may churn out code, but isn’t always accurate; deploying AI in critical applications without human oversight is a recipe for disaster. Developers are essential for identifying and correcting errors to ensure the integrity and functionality of the software.

Devin AI may have its uses but it’s far from the panacea it’s been made out to be. As software engineers we should embrace innovation but remain skeptical of overhyped technologies. After all, it’s our expertise and ingenuity that will continue to drive progress in the field, not flashy AI gimmicks.

Play Updates

Mar 30, 2024

  • Improved compatibility of option input and free-text input.
  • Fix issue where completed puzzle parts were not saving to the browser.

Mar 23, 2024

  • Fixed the issue where Mar 23, 2024 daily puzzle didn’t have the correct option.

Shuffling algorithm in 1 line instead of 10: functional Fisher-Yates

Can we write the Fisher-Yates shuffling algorithm in a declarative and functional way with zero mutations, all in a single one-liner statement?!

Let’s find out.

The normal way of writing Fisher-Yates

Some time ago I was creating an indie card game for Android and I needed to shuffle the cards before sharing them to the user and opponents.

Well I didn’t know about the standard shuffling algorithms, so after some thought I came up with this:

But after reading more about Fisher-Yates on Wikipedia, I discovered serious problems with my algorithm:

JavaScript
// It was in C#, but like this function shuffleArray(array) { const clone = [...array]; for (let i = clone.length - 1; i > 0; i--) { // Swap i with random item from 0..n const randomIndex = Math.floor( Math.random() * clone.length // โŒโŒ ); const temp = clone[i]; clone[i] = clone[randomIndex]; clone[randomIndex] = temp; } return clone; } console.log(shuffleArray(['C', 'I', 'O', 'L', 'G'])); // [ 'I', 'L', 'O', 'G', 'C' ] console.log(shuffleArray(['C', 'I', 'O', 'L', 'G'])); // [ 'L', 'O', 'G', 'I', 'C' ]

I was swapping each item with a random element in the range of 0..n, but this was wrong.

As explained here, this made it more likely for the array to get shuffled in particular ways.

The right way was to use the range 0..i to swap with the current element i in the loop.

JavaScript
function shuffleArray(array) { const clone = [...array]; for (let i = clone.length - 1; i > 0; i--) { const randomIndex = Math.floor(Math.random() * (i + 1)); const item = clone[i]; clone[i] = clone[randomIndex]; clone[randomIndex] = item; } return clone; } console.log(shuffleArray(['C', 'I', 'O', 'L', 'G'])); // [ 'G', 'L', 'I', 'C', 'O' ] console.log(shuffleArray(['C', 'I', 'O', 'L', 'G'])); // [ 'L', 'O', 'G', 'I', 'C' ]

With this I could make sure every possible shuffled result would have an equal chance of happening.

Functional, immutable, one-liner Fisher-Yates

How exactly are we supposed to go about this?

Can we try this?๐Ÿ‘‡

JavaScript
const shuffleArray = (arr) => arr.reduce((array, item, i) => { const randomIndex = Math.floor( Math.random() * arr.length ); [arr[i], arr[randomIndex]] = [arr[randomIndex], arr[i]]; return arr; });

No we can’t; We used ES6 swapping to shorten the code but we still mutated the array. And cloning doesn’t count.

We need to figure out a way to swap the array elements immutably – create a new array with items swapped.

Using conditionals we can easily come up with this:

JavaScript
const immutableSwap = (arr, i, j) => arr.map((item, index) => index === i ? arr[j] : index === j ? arr[i] : item ); const arr = ['B', 'E', 'A', 'U', 'T', 'Y']; console.log(immutableSwap(arr, 2, 4)); // [ 'B', 'E', 'T', 'U', 'A', 'Y' ]

But we could also use Object.values like this:

JavaScript
const immutableSwap = (arr, i, j) => { return Object.values({ ...arr, [i]: arr[j], [j]: arr[i], }); }; console.log(immutableSwap(arr, 3, 5)); // [ 'B', 'Y', 'A', 'U', 'T', 'E' ]

What’s next?

With the immutable swap worked out, our game plan is pretty straightforward:

  1. For each item i in 0..n, immutably select an index in 0..i to swap with i for each i in 0..n
  2. Loop through the array again and immutably swap each element with its assigned swapping pair.

For #1 we can easily use map() to create an array with the new random positions for every index:

JavaScript
const getShuffledPosition = (arr) => { return [...Array(arr.length)].map((_, i) => Math.floor(Math.random() * (i + 1)) ); }; /* [0, 2, 0, 1] swap item at: 1. index 0 with index 0 2. index 1 with index 2 3. index 2 with index 0 4. index 3 with index 1 */ getShuffledPosition(['๐Ÿ”ต', '๐Ÿ”ด', '๐ŸŸก', '๐ŸŸข']); getShuffledPosition(['๐Ÿ”ต', '๐Ÿ”ด', '๐ŸŸก', '๐ŸŸข']); // [0, 1, 1, 3] getShuffledPosition(['๐Ÿ”ต', '๐Ÿ”ด', '๐ŸŸก', '๐ŸŸข']); // [0, 0, 2, 1]

What about #2?

We’re outputting an array, but we can’t use map again because the transformation at each step depends on the full array and not just the current element.

So we’ll use reduce():

JavaScript
const shuffleUsingPositions = (arr) => getShuffledPosition(arr).reduce( (toShuffle, newPosition, originalIndex) => immutableSwap(toShuffle, originalIndex, newPosition), arr ); shuffleUsingPositions(['๐Ÿ”ต', '๐Ÿ”ด', '๐ŸŸก', '๐ŸŸข']) // [ '๐Ÿ”ด', '๐Ÿ”ต', '๐ŸŸข', '๐ŸŸก' ]

Not let’s de-abstract the immutable functions so we have a true and complete one-liner:

JavaScript
const shuffleArray = (arr) => [...Array(arr.length)] .map((_, i) => Math.floor(Math.random() * (i + 1))) .reduce( (toShuffle, newPosition, originalIndex) => toShuffle.map((item, index) => index === originalIndex ? toShuffle[newPosition] : item === newPosition ? originalIndex : item ), arr );

Or – faster and more readable:

JavaScript
const shuffleArray = (arr) => [...Array(arr.length)] .map((_, i) => Math.floor(Math.random() * (i + 1))) .reduce( (toShuffle, newPosition, originalIndex) => Object.values({ ...toShuffle, [originalIndex]: toShuffle[newPosition], [newPosition]: toShuffle[originalIndex], }), arr );
JavaScript
shuffleArray(['๐Ÿ”ต', '๐Ÿ”ด', '๐ŸŸก', '๐ŸŸข']) // (3x) // [ '๐ŸŸก', '๐Ÿ”ด', '๐Ÿ”ต', '๐ŸŸข' ] // [ '๐Ÿ”ด', '๐ŸŸก', '๐ŸŸข', '๐Ÿ”ต' ] // [ '๐ŸŸก', '๐Ÿ”ต', '๐Ÿ”ด', '๐ŸŸข' ]

Just like you can always write a recursive algorithm as a loop, you can write every imperative iteration as a brilliant single-statement chain of array methods.

They all work perfectly.