featured

5 reasons why I will never autosave code again (and why you shouldn’t)

Used to be a fan but the pains started outweighing the gains and I had to turn it off forever.

1. I actually don’t have unlimited CPU and RAM

You can’t dare call VS Code a “text editor” in 2023; The bloat is real.

Thanks to Electron, we now live in an era where a near-empty app boasts installation sizes of almost 100MB and proudly gobbles as much as 500MB of RAM.

My dozens of extension probably has something to do with this, but many of these extensions only run for specific types of projects and VS Code commands.

My Dart project here uses only 1 of these (the obvious one), and almost none of the ones coming after.

And none of this 1,200 MB RAM and 16.9% CPU came from my running anything in the terminal! I’m sure that there are some background tasks here and there, but I was doing absolutely nothing — no typing, no building, no long-running tasks.

So what do you think will happen when a build watcher like Create React App is also running? Or a massive memory hog like the Android emulator, constantly reloading on every autosave?

2. Where’s our formatting

VS Code doesn’t care about auto-save in its “auto-format on save” feature.

And why should it happen when you’ve just barely finished typing?

Format-on-save only makes sense for real saves; you usually only press Ctrl + S when you’ve reached some sort of checkpoint; like after a bug fix attempt or a UI change and you want to see our changes in action.

If I want auto-formatting I’m still going to be using Ctrl + D, D my personal keyboard shortcut for auto-formatting. So, no thanks — I’ll rather save and format together.

3. Autosave demands perfection

Autosave wants you to be superhuman.

There’s no breathing room to make mistakes with auto-save; any you type gets written to disk within seconds.

Like can’t you even make a single typo? Can’t you have doubts about which direction to take the code in?

Now you’ll say there’s Git and all, but here’s something to think about: within every commit, there are multiple “micro-commits”. Even if you’re blindly committing every single day there’ll be intermediary changes within those 24 hours that you won’t want to compromise or discard.

With autosave you’ll never get a warning when you close a file after making an unwanted and buggy change — potentially leading to hours of debugging code you thought was working just a few days back.

4. A save should be a micro-commit

Even if you Ctrl + S all the time you’ll probably wait until the end of the statement. Because subconsciously you see Ctrl + S as a micro-commit, and it would be senseless to make those micro-commits at random.

But build watchers like Create React App don’t see it this way.

These tools couldn’t care less about whether the code that’s been auto-saved is complete or not; they just re-build and re-build and re-build again.

Not cool. And at times this put pressure on me to keep typing until I was ready to see the result of my changes.

That’s distracting and unnatural. And you can’t avoid it — the best you can you is to set an autosave delay to reduce how much this happens or close the terminal. The former comes with its own set of problems (as we’ll see), and the latter is simply unthinkable.

5. The lag tradeoff is annoying

Imagine what would happen if VS Code and others weren’t sensible enough to have a delay before any autosave?

Your CPU usage would go crazy; Even if you’re an extra-terrestrial typing god going at 10000 words per minute, you’re still not going to be fast enough; there’ll be a disk write for every single character you put down.

Even worse when you have a heavy build tool like Create React App watching your files and re-building.

So yes there had to be an autosave delay.

But a delay is a delay and no one likes waiting, even if it’s a 3-second delay. Especially when you can get rid of it.

When I press Ctrl + S, I instantly know my changes are saved. I know my build watcher will run immediately. I have instant feedback and there is zero noticeable delay.

I find small things like these make a non-trivial impact on the development experience.

Final thoughts

Comic on Ctrl + S fatigue.
Image source: CommitStrip

So I’m no longer autosaving my code. I had it on for some months back after I discovered such a feature even existed in VS Code, and then I went on and off, and then finally — no more! Like many devs, I’m already used to Ctrl + S so it’s no big deal. Let’s see if my mind changes in the future — how about you?

Coding as digital creations growing into greatness

What if we see coding as a delicate act of nurturing, a digital development brought about by our creativity and the intricate logical reasoning of our minds?

There’s a reason we’re called software *developers*. Developers develop; developers build and grow.

If we can get ourselves to view a software project as a unit of its own, with all its capabilities, potential, and goals, we instantly start to observe numerous interesting connections between programming and other nurturing acts like parenting, gardening, and self-improvement.

From the onset: a freshly scaffolded project, a file, or a folder. There is always a beginning, a birth.

With every single line of code added, deleted, and refactored, the software grows into greatness. With every additional library, package, and API fastened into place, low-level combines into high-level; the big picture unfolds; a fully formed digital creation emerges.

Goal in sight: Plan

Similar to nurturing a budding sapling, the coding process begins with the seed of an idea. You envision the type of product you hope your software will become.

You and your team are to carefully plan and design the structure, algorithms, and flow of the code, much like parents are to create a loving and supportive home by providing the necessary resources, values, and guidance to foster their child’s development.

This thoughtful approach lays the foundation for the entire coding project and boosts the chances of a bright future.

From here to there: Grow

Your codebase needs to grow and improve, but growth on its own is a very vague and generic word. It’s just… an increase.

For any type of growth of any entity to be meaningful, it has to be in the direction of *something*. Something valuable and important.

What’s the first thing I get right now when I search for the meaning of “personal” growth on Google?

This definition prefers to remain vague and makes it clear that personal growth isn’t the same for everyone. You choose your values, set goals based on them, and develop yourself and your environment according to those goals.

But this doesn’t mean you can pick just any goal you want for a happy life; Certain goals are way more beneficial for your well-being than others. A target of progressively gobbling up to 10,000 calories of ice cream every day can never be put in the same league as running a marathon or building solid muscle at the gym.

So the same thing applies to coding. Consciously hoping to write 10,000 lines of code or add 1,000 random features to your software is a terrible aspiration, and will never guarantee high-quality software.

Apart from the explicitly defined requirements, here are some important growth metrics to measure your software project by:

On a lower level, all about the code:

  1. Readability: How easy is it for a stranger to understand your code, even if they know nothing about programming?
  2. Efficiency: How many units of a task can your program complete in X amount of time?
  3. Modularity: Is your code organized into separate modules for different responsibilities? Can one module be swapped out without the entire project crashing into ruins?
  4. Testability and test quality & quantity: How easy is it to test these modules? Do you keep side-effects and mutation to a minimum? And yes, how many actual tests have you written? Are these tests even useful and deliberate? Do you apply the 80/20 rule during testing?

On a higher level closer to the user:

  1. Effective, goal-focused communication: with copy, shape, icons, and more.
  2. Perceived performance: Including immediate responses to user actions, loading bars for long-running actions, buffering, etc.
  3. Number of cohesive features: Are the features enough, and relevant to the overall goal of the software?
  4. Sensual pleasure: Lovely aesthetics, stunning animations, sound effects, and more.
Was working on some open-source download manager a few years back.

This delightful growth process is one of the things that transforms coding from a cold logical activity into a creative, empowering, addictive pursuit. You continually strive, with the ultimate desired outcome in sight, line after line, commit after commit, feature after feature; everlasting progress in an energizing purposeful atmosphere.

What man actually needs is not a tensionless state but rather the striving and struggling for some goal worthy of him. What he needs is not the discharge of tension at any cost, but the call of a potential meaning waiting to be fulfilled by him.

Viktor E. Frankl (1966). “Man’s Search for Meaning”

And yes, just like raising a responsible, well-rounded individual, we may never actually reach a definite, final destination. But it gave us a strong sense of direction. We are going *somewhere* and we have the map.

Have you heard the popular estimates of devs spending on average as much as 75% of their time debugging software?

Yes, it would be nothing but a pipe dream to expect this journey to be easy.

Indeed, as a loving parent does all they can to shield their dearest child from harm’s way, programmers must safeguard their code from bugs and vulnerabilities to produce robust and reliable software. We’ve already talked about testing, and these precautions drastically reduce the chances of nasty errors and mistakes.

But just like with building good personal habits and social skills, you are still going to make mistakes along the way.

With any serious complex codebase, there are still going to be loud, frustrating bugs that bring you to the point of despair and hopelessness. But you simply cannot quit. You are responsible; you are the architect of this virtual world, the builder of digital solutions and innovations. You possess the power to overcome obstacles and transform difficulties into opportunities for growth — your growth, your software’s growth.

And what of when the path toward the goal changes dramatically? Close-mindedness won’t take you far.

Parents need to be adaptable and flexible in adjusting to the child’s changing unique needs and development stages: Software requirements are hardly set in stone. You must be able to incorporate new features into your codebase — and destroy them as time goes on.

Pro-active scalability is nice to have in every software system, but this isn’t always feasible; in the real world you have time and cost constraints and you need to move fast. What was a great algorithm at 1 thousand users could quickly become a huge performance bottleneck at 1 million users. You may need to migrate to a whole new database for increasingly complex datasets needing speedy retrieval.

Takeaway: What coding should be

Like tending to a seedling or guiding a child’s development, coding requires careful planning, thoughtful design, and continuous adaptation. It’s an everlasting process of meaningful growth, measured not only by lines of code but by metrics such as readability, efficiency, beauty, and user-focused features. With each line added, bugs debugged, and obstacles overcome, the software and its creator thrive together on a path filled with purpose, determination, and endless possibilities.

Top 10 new JavaScript features from 2021 to 2023

These 10 amazing features were all about writing shorter, safer, and more expressive code.

1. Private methods and fields

We need our privacy. There’s no two ways about it.

And so do OOP classes; but in JavaScript it was once impossible to strictly declare private members.

It was once impossible to declare private members in a JavaScript class.

A member was traditionally prefixed with an underscore (_) to indicate that it was meant to be private, but it could still be accessed and modified from outside the class.

JavaScript
class Person { _firstName = 'Coding'; _lastName = 'Beauty'; get name() { return `${this._firstName} ${this._lastName}`; } } const person = new Person(); console.log(person.name); // Coding Beauty // Members intended to be private can still be accessed // from outside the class console.log(person._firstName); // Coding console.log(person._lastName); // Beauty // They can also be modified person._firstName = 'Debugging'; person._lastName = 'Nightmares'; console.log(person.name); // Debugging Nightmares

With ES2022, we can now add private fields and members to a class, by prefixing it with a hashtag (#). Trying to access them from outside the class will cause an error:

JavaScript
class Person { #firstName = 'Coding'; #lastName = 'Beauty'; get name() { return `${this.#firstName} ${this.#lastName}`; } } const person = new Person(); console.log(person.name); // SyntaxError: Private field '#firstName' must be // declared in an enclosing class console.log(person.#firstName); console.log(person.#lastName);

Note that the error thrown here is a syntax error, which happens at compile time; the code doesn’t run at all; the compiler doesn’t expect you to even try to access private fields from outside a class, so it assumes you’re trying to declare one.

“Ergonomic brand” checks for private fields

With private fields come a new keyword to safely check if a class object contains a particular one — the in keyword:

JavaScript
class Car { #color; hasColor() { return #color in this; } } const car = new Car(); console.log(car.hasColor()); // true;

It correctly distinguishes private fields with the same names from different classes:

JavaScript
class Car { #color; hasColor() { return #color in this; } } class House { #color; hasColor() { return #color in this; } } const car = new Car(); const house = new House(); console.log(car.hasColor()); // true; console.log(car.hasColor.call(house)); // false console.log(house.hasColor()); // true console.log(house.hasColor.call(car)); // false

And don’t ask me about the name; I also have no idea why they’re called that (do you?).

Ergonomics as far as I’m concerned is all about keeping good sitting posture while using your computer (?) 🤔.

Although I guess you could twist this definition to allow for this new feature — or any new feature for that matter. They’re all about comfort, right? Less pain writing code.

But I guess English isn’t a closed language and you can always add new words and additional definitions (just ask Shakespeare).

2. Immutable sort(), splice(), and reverse()

ES2023 came fully packed with immutable versions of these 3 heavily used array methods.

Okay maybe splice() isn’t used as much as the others, but they all mutate the array in place.

JavaScript
const original = [5, 1, 3, 4, 2]; const reversed = original.reverse(); console.log(reversed); // [2, 4, 3, 1, 5] (same array) console.log(original); // [2, 4, 3, 1, 5] (mutated) const sorted = original.sort(); console.log(sorted); // [1, 2, 3, 4, 5] (same array) console.log(original); // [1, 2, 3, 4, 5] (mutated) const deleted = original.splice(1, 2, 7, 10); console.log(deleted); // [2, 3] (deleted elements) console.log(original); // [1, 7, 10, 4, 5] (mutated)

Immutability gives us predictable and safer code; debugging is much easier as we’re certain variables never change their value.

Arguments are exactly the same, with splice() and toSpliced() having to differ in their return value.

JavaScript
const original = [5, 1, 3, 4, 2]; const reversed = original.toReversed(); console.log(reversed); // [2, 4, 3, 1, 5] (copy) console.log(original); // [5, 1, 3, 4, 2] (unchanged) const sorted = original.toSorted(); console.log(sorted); // [1, 2, 3, 4, 5] (copy) console.log(original); // [5, 1, 3, 4, 2] (unchanged) const spliced = original.toSpliced(1, 2, 7, 10); console.log(spliced); // [1, 7, 10, 4, 5] (copy) console.log(original); // [5, 1, 3, 4, 2] (unchanged)

3. Top-level await

Did you know: F# was the first language to introduce async/await? As far back as 2007! But it took JavaScript 10 good years to catch up.

await pauses execution in the async context until a Promise resolves.

Previously we could only use this operator in an async function, but it could never work in the global scope.

JavaScript
function setTimeoutAsync(timeout) { return new Promise((resolve) => { setTimeout(() => { resolve(); }, timeout); }); } // SyntaxError: await is only valid in async functions await setTimeoutAsync(3000);

With ES2022, now we can:

JavaScript
function setTimeoutAsync(timeout) { return new Promise((resolve) => { setTimeout(() => { resolve(); }, timeout); }); } // ✅ Waits for timeout - no error thrown await setTimeoutAsync(3000);

4. Promise.any()

If you know Promise.all(), then you can easily guess what this does: wait for one Promise to resolve and return the result.

JavaScript
async function getHelpQuickly() { const response = await Promise.any([ cautiousHelper(), kindHelper(), wickedHelper(), ]); console.log(response); // Of course! } async function cautiousHelper() { await new Promise((resolve) => { setTimeout(() => { resolve('Uum, oohkaay?'); }, 2000); }); } async function kindHelper() { return 'Of course!'; } function wickedHelper() { return Promise.reject('Never, ha ha ha!!!'); }

Point to note: Promise.any() still waits for *all* the promises in the current async context to resolve, even though it only returns the result of the first one.

JavaScript
await getHelpQuickly(); // outputs "Of course!" immediately // Still waits for 2 seconds

5. Array find from last

Array find() searches for an array element that passes a specified test condition, and findIndex() gets the index of such an element.

While find() and findIndex() both start searching from the first element of the array, there are instances where it would be preferable to start the search from the last element instead.

There are scenarios where we know that finding from the last element might achieve better performance. For example, here we’re trying to get the item in the array with the value prop equal to y. With find() and findIndex():

JavaScript
const letters = [ { value: 'v' }, { value: 'w' }, { value: 'x' }, { value: 'y' }, { value: 'z' }, ]; const found = letters.find((item) => item.value === 'y'); const foundIndex = letters.findIndex((item) => item.value === 'y'); console.log(found); // { value: 'y' } console.log(foundIndex); // 3

This works, but as the target object is closer to the tail of the array, we might be able to make this program run faster if we use the new ES2022 findLast() and findLastIndex() methods to search the array from the end.

JavaScript
const letters = [ { value: 'v' }, { value: 'w' }, { value: 'x' }, { value: 'y' }, { value: 'z' }, ]; const found = letters.findLast((item) => item.value === 'y'); const foundIndex = letters.findLastIndex((item) => item.value === 'y'); console.log(found); // { value: 'y' } console.log(foundIndex); // 3

Another use case might require that we specifically search the array from the end to get the correct item.

If we’re finding the last even number in a list of numbers, find() and findIndex() produces a totally wrong result:

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

We could call the reverse() method on the array to reverse the order of the elements before calling find() and findIndex(). But this approach would cause unnecessary mutation of the array; reverse() reverses the elements of an array in place.

The only way to avoid this mutation would be to make a new copy of the entire array, which could cause performance problems for large arrays.

Also findIndex() still wouldn’t on the reversed array, as reversing the elements would also mean changing the indexes they had in the original array. To get the original index, we would need to perform an additional calculation, which means writing more code.

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

It’s cases like where the findLast() and findLastIndex() methods come in handy.

JavaScript
const nums = [7, 14, 3, 8, 10, 9]; const lastEven = nums.findLast((num) => num % 2 === 0); 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.

6. String replaceAll()

We already had replace() for quickly replace a substring within a string.

JavaScript
const str = 'JavaScript is so terrible, it is unbelievably terrible!!'; const result = str.replace('terrible', 'wonderful'); console.log(result); // JavaScript is so wonderful, it is unbelievably terrible!! // Huh?

But it only does so for the first occurrence of the substring unless you use a regex; now we have replaceAll() to replace every single instance of that substring.

JavaScript
const str = 'JavaScript is so terrible, it is unbelievably terrible.'; const result = str.replaceAll('terrible', 'wonderful'); console.log(result); // JavaScript is wonderful, it is unbelievably wonderful. // Now you're making sense!

7. Array with() and at()

at() came first and with() came a year after that in 2023.

They are the functional and immutable versions of single-element array modification and access.

JavaScript
const colors = ['pink', 'purple', 'red', 'yellow']; console.log(colors.at(1)); // purple console.log(colors.with(1, 'blue')); // ['pink', 'blue', 'red', 'yellow'] // Original not modified console.log(colors); // ['pink', 'purple', 'red', 'yellow']

The cool thing about these new methods is how they let you get and change element values with negative indexing.

JavaScript
// index -N is same as index arr.length - N const fruits = ['banana', 'apple', 'orange', 'butter???']; console.log(fruits.at(-3)); // apple console.log(fruits.at(-1)); // butter??? console.log(fruits.with(-1, 'watermelon')); // ['banana', 'apple', 'orange', 'watermelon'] ✅

8. static static static

Static class fields, static private methods (2022).

Static methods access other private/public static members in the class using the this keyword; instance methods with this.constructor:

JavaScript
class Person { static #count = 0; static getCount() { return this.#count; } // Instance method constructor() { this.constructor.#incrementCount(); } static #incrementCount() { this.#count++; } } const person1 = new Person(); const person2 = new Person(); console.log(Person.getCount()); // 2

Static blocks.

Executed only once when the *class* is created. It’s like static constructors in other OOP languages like C# and Java.

JavaScript
class Vehicle { static defaultColor = 'blue'; } class Car extends Vehicle { static colors = []; static { this.colors.push(super.defaultColor, 'red'); } static { this.colors.push('green'); } } console.log(Car.colors); // [ 'blue', 'red', 'green' ]

When they’re multiple static blocks, they’re executed in the order they’re declared, along with any static fields in between. The super property in a static block to access properties of the superclass.

9. Logical assignment operators

They let a variable perform a logical operation with another variable and re-assign the result to itself.

We use them like this:

JavaScript
left ??= right; left ||= right; left &&= right;

They’re as good as:

JavaScript
// More like exactly the same as left = (left ?? right); left = (left || right); left = (left && right);

??=. Quickly assign a value to a variable *if* it is null or undefined (“nullish”).

JavaScript
user.preferredName ??= generateDumbUserName();

||=. Like ??=, but assigns the value for any falsy value (0, undefined, null, '', NaN, or false).

JavaScript
user.profilePicture ||= "/angry-stranger.png";

And then &&=. Something like the reverse; only assigns when the value is truthy (not falsy).

JavaScript
user.favoriteLanguage = await setFavoriteLanguage(input.value); user.favoriteLanguage &&= 'Assembly'; // You're lying! It's Assembly!

10. Numerical separators

Tiny new addition to make big number literals more readable and human-friendly.

JavaScript
const isItPi = 3.1_415_926_535; const isItAvagadro = 602_214_076_000_000_000_000_000; const isItPlanck = 6.626_070_15e-34; const isItG = 6.674_30e-11; // Works for other number bases too...

The compiler completely ignores those pesky underscores — they’re all for you, the human!

Final thoughts

These are the juicy new JavaScript features that arrived in the last 3 years. Use them to boost your productivity as a developer and write cleaner code with greater conciseness, expressiveness and clarity.

Master JavaScript generators: 5 inspiring practical use cases

You will be an expert on JavaScript generators by the time you’re done reading this.

JavaScript generators are way more than just a fancy feature and we are going to discover many powerful use cases for them, including creating engaging animations, and streaming videos over the internet.

If you’ve never heard of them you may be missing out.

Generators are simply functions that you can pause and resume whenever you want — they don’t execute continuously.

The asterisk * marks the function as a generator, and yield generates values on demand from a .next() call, until the generator is done.

JavaScript
function* myGenerator() { yield 'Hello'; yield 'World'; } const iterator = myGenerator(); console.log(iterator.next()); // {value: 'Hello', done: false} console.log(iterator.next()); // {value: 'World', done: false} console.log(iterator.next()); // {value: undefined, done: true}

It’s just like how a physical generator produces electricity not all at once, but as time goes on.

Instead of calling next() you can use the for..of loop, which is great for when the generator generates a lot of data.

JavaScript
function* countTo100() { for (let i = 0; i < 100; i++) { yield i; } } for (const item of countTo100()) { console.log(item); } // 1, 2, ... 100

Lazy evaluation

“Calculate only when necessary.”

Much unlike regular functions in JavaScript that execute entirely and return the result.

Let’s say you want a sequence of numbers, but you’re not sure how many. Here’s how a generator helps:

JavaScript
function* numberGenerator() { let number = 1; // infinite loop won't cause freeze in generator // -- execution pauses after `yield` while (true) { yield number++; } } const numbers = numberGenerator(); console.log(numbers.next().value); // 1 console.log(numbers.next().value); // 2 // you can continue this as long as you need

With generators, you get the next number only when you ask for it.

Better memory utilization

Generators don’t hold all the results in memory, they generate them on the fly.

Imagine you need a sequence of a million numbers. With a regular function, you’d need to store all these numbers in an array, using up significant memory.

A generator is much more efficient:

JavaScript
function* bigNumberGenerator() { let number = 1; while (number <= 100000000) { yield number++; } } const bigNumbers = bigNumberGenerator(); const chunk = 10; for (let i = 0; i < chunk; i++) { const value = bigNumbers.next().value; // Use next 10 values... }

Handling asynchronous tasks

Did you know that Babel transpiles async/await to generators for JavaScript versions that don’t support it natively?

Babel tranforms this:

JavaScript
this.it('is a test', async function () { const name = await 'coding beauty' const num = await new Promise(resolve => resolve(10)); console.log(`Name: ${name}, Num: ${num}`); });

To this:

JavaScript
function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, arguments); return new Promise(function (resolve, reject) { function step(key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { return Promise.resolve(value).then( function (value) { return step('next', value); }, function (err) { return step('throw', err); } ); } } return step('next'); }); }; } myFunc( 'generator async/await example', _asyncToGenerator(function* () { const name = yield 'coding beauty'; // yield, not await const num = yield new Promise((resolve) => resolve(10)); console.log(`Name: ${name}, Num: ${num}`); }) );

Typing animations

Typing animations grab the attention of users and make your website more visually appealing.

They add personality and character to a website by mimicking the typing behavior of a human to create a more human-like experience and establish a unique brand identity.

So with all these benefits you’re feeling excited about infusing your webpages with these energetic visual effects.

Here would be a decent way to go about it, using recursion and setTimeout():

JavaScript
function typeAnimation(text, index = 0) { if (index < text.length) { document.body.innerHTML += text.charAt(index); index++; setTimeout(typeAnimation, 500); } }

But, it’s cases like this where generators shine:

JavaScript
function* typeAnimation(text) { for(let char of text) { yield char; } }

Since we can generate values whenever we want, we can do this in time intervals using setInterval().

JavaScript
const generatorInstance = typeGenerator("Coding Beauty"); function startTypingEffect(elementId, generatorInstance) { let element = document.getElementById(elementId); setInterval(() => { let result = generatorInstance.next(); !result.done && element.innerHTML += result.value; }, 100); } startTypingEffect("type-here", generatorInstance);
JavaScript
<div id="type-here"></div>

Asynchronous handling

Note: This is NOT the same as being the bedrock of async/await, like we saw earlier. We’re talking about async generators here.

JavaScript
function* asyncGenerator() { yield new Promise(resolve => setTimeout(() => resolve('Task 1 completed'), 2000)); yield new Promise(resolve => setTimeout(() => resolve('Task 2 completed'), 3000)); // ... }

And here’s how we can use this generator:

JavaScript
const asyncGen = asyncGenerator(); asyncGen.next().value.then(console.log); asyncGen.next().value.then(console.log);

This is such a powerful tool for streaming data in web app in a structured, readable manner — just look at this function that buffers and streams data for a video-sharing app like YouTube:

JavaScript
async function* streamVideo({ id }) { let endOfVideo = false; const downloadChunk = async (sizeInBytes) => { const response = await fetch( `api.example.com/videos/${id}` ); const { chunk, done } = await response.json(); if (done) endOfVideo = true; return chunk; }; while (!endOfVideo) { const bufferSize = 500 * 1024 * 1024; yield await downloadChunk(bufferSize); } }

To consume this async generator, we’d use the for await..of loop:

JavaScript
for await (const chunk of streamVideo({ id: 2341 })) { // process video chunk }

redux-saga

redux-saga is a library for managing side effects in applications, boasting over 1 million weekly downloads.

Generators play a big role in this library, handling the redux actions to make testing and error handling easier.

Take a look at this simple saga:

JavaScript
import { call, put, takeEvery } from 'redux-saga/effects'; function* fetchData(action) { try { const data = yield call(Api.fetchUser, action.payload.userId); yield put({type: "USER_FETCH_SUCCEEDED", data}); } catch (e) { yield put({type: "USER_FETCH_FAILED", message: e.message}); } } function* mySaga() { yield takeEvery("USER_FETCH_REQUESTED", fetchData); }
JavaScript
import createSagaMiddleware from 'redux-saga'; import { createStore, applyMiddleware } from 'redux'; import mySaga from './sagas'; const sagaMiddleware = createSagaMiddleware(); const store = createStore( reducer, applyMiddleware(sagaMiddleware) ); sagaMiddleware.run(mySaga)

Whenever the USER_FETCH_REQUESTED action is dispatched, redux-saga runs the generator which in turn calls fetchData() to perform the asynchronous network request.

A note on return

What happens when you return a value in a generator function? Let’s see:

JavaScript
function* soccerPlayers() { yield 'Ronaldo'; yield 'Messi'; return 'Neymar'; } for (const player of soccerPlayers()) { console.log(player); } // Ronaldo // Messi

Why isn’t Neymar part of the generated values?

Let’s use .next() to find out if the done property has something to do with it:

JavaScript
function* soccerPlayers() { yield 'Ronaldo'; yield 'Messi'; return 'Neymar'; } const playerGen = soccerPlayers(); console.log(playerGen.next()); console.log(playerGen.next()); console.log(playerGen.next()); /* { value: 'Ronaldo', done: false } { value: 'Messi', done: false } { value: 'Neymar', done: true } */

It turns out that return is not considered a generated value, so for..of doesn’t process it.

Do you remember our very first example:

JavaScript
function* myGenerator() { yield 'Hello'; yield 'World'; } const iterator = myGenerator(); console.log(iterator.next()); // {value: 'Hello', done: false} console.log(iterator.next()); // {value: 'World', done: false} console.log(iterator.next()); // {value: undefined, done: true}

You can see that generators only produce values until, but not including when done is true.

So return completes the generator and terminates the function (like any other).

JavaScript
function* myGenerator() { yield 'Hello'; return 'End'; yield 'This will not be executed'; }

Final thoughts

JavaScript generators offer powerful solutions for control flow, memory efficiency, and asynchronous handling. They enhance web development with dynamic animations, streaming data, and managing side effects.

Let’s embrace the versatility of generators for elegant and efficient JavaScript programming.

How to always be excited about coding

“Don’t code for code’s sake!”

“The code doesn’t matter — only programs do…”

“Coding is not creative it’s logic…”

The thing is many programmers simply don’t see how coding can be creative or intrinsically interesting.

And you know it’s not so easy to stay excited if your days revolve mostly around building apps that do little more than fetch data from a database and display it to the users for them to change.

That can become so repetitive after a while, even worse when you’re using the same tech stack over and over.

Not any better when you find yourself constantly copying and pasting from StackOverflow — programming becomes a little more than a routine chore, rather than an intensive exercise promoting genuine problem-solving and creativity.

And maybe your job demands that you act this way – with all the dry business requirements and the tight deadlines.

But this won’t help you reap the great joys that abound in programming or experience the satisfaction and empowerment that come from solving complex problems and creating something from nothing.

To gain the maximum emotional benefits, you must focus on the process as much as the goal and realize that coding is more than just a means to an end.

“Personal” creativity and mental engagement

What if we see coding as a creative art form that allows abstract thoughts and concepts to materialize into tangible and functional systems?

Making a mark on the broad canvas of our code editor with the paintbrushes of our programming languages and frameworks.

It may be hard to see a connection between coding and creativity when there are now well-established algorithms for just about every logical/mathematical problem that could crop up during development. It seems like there’s nothing new under the programming sun.

Yet I don’t see why this should matter. From the perspective of personal enjoyment, the only creativity that matters is personal creativity. The knowledge brought forth from your mind, compared to what’s already in there. Relative novelty: The difference what you’re subjectively aware of and what you’re not.

Just think of movies. Right now, Fast X is like the only Fast and Furious movie I’m yet to see.

And when I finally do get to see it, would it matter how many other thousands and millions of people have seen the movie? That they’ve had those new experiences doesn’t rob *me* of them – as long they don’t cough up spoilers.

What if you’re tackling a math proof or proving a complex theorem and you come up with some astoundingly brilliant solution? Even if the theorem was already well-known in the math community and proven solutions have existed since time immemorial, the fact is *you* never knew it. You will still experience mental engagement and positive challenges as you immerse yourself in the task.

That fiery spark of mental engagement igniting the depth of your intellect, kindling a boundless flow, carrying you for hours, transcending the realms of time — sleep becomes a distant memory. It’s a mesmerizing force holding you at the edge of your seat, empowering passion and unwavering dedication to your craft.

Contrary to what we usually believe, moments like these, the best moments in our lives, are not the passive, receptive, relaxing times—although such experiences can also be enjoyable, if we have worked hard to attain them. The best moments usually occur when a person’s body or mind is stretched to its limits in a voluntary effort to accomplish something difficult and worthwhile. Optimal experience is thus something that we make happen.

Mihaly Csikszentmihalyi, Flow: The Psychology of Optimal Experience

How to enjoy programming

To truly appreciate coding as more than just a mere job, but instead an exciting and fulfilling pursuit, it is important for us to find genuine pleasure and motivation in it, beyond the external rewards of monetary gain and fame.

1. Implement new algorithms

Yes you know quick sort and depth-first search but what about the thousands of other algorithms you’ve probably never heard of?

Read up on the high-level details of a particular named algorithm and try writing it yourself — and don’t use StackOverflow or any code completion tool for this one, because now the process matters as much as the goal.

Challenging yourself to learn and implement new algorithms keeps your brain active and promotes intellectual growth. It enhances your programming proficiency, providing you with a deep understanding of various languages and constructs.

By diving into the intricacies of algorithms, you’re peeling back the layers of abstraction present in many coding libraries, promoting a profound grasp of these underlying tools’ architectures and mechanisms.

This understanding and skill set encourage innovative thinking, enabling you to formulate new combinations or adaptations of these algorithms. Most importantly, the excitement and satisfaction derived from the successful execution of these complex codes can be quite exhilarating, adding pleasure to your programming pursuits.

2. Solve interesting word problems

It doesn’t matter whether it’s Math, Physics, or Programming.

Word problems are always an incredible way to challenge your cognitive functions especially as they test your ability to process all the information and convert it into software requirements and then into code.

You have to carefully break down various tasks into smaller, manageable components, and then identify the appropriate data structures, control structures, and operations needed to achieve all of them. Successfully going through all of these not only gives you a deep sense of accomplishment (which could vary depending on the complexity of the problem), but it also drastically improves your logic, abstraction, and critical thinking skills.

Where to find these types of problems?

Coding Beauty Play is one — you’ll discover engaging puzzles that boost your coding skills, train your brain, and keep you mentally sharp.

Regularly updated with at least one new puzzle a week.

3. Create your own game

There’s no denying how cool they can be. Even addictive for some.

It’s not uncommon to hear stories of how the beauty and splendor of games were what inspired people to go into programming in the first place. Some of us even had albeit unrealistic but passionately ambitious game development goals – like I remember how I wanted to create my own 3D football game by myself at age 9 with pure C++.

Whether it’s a sci-fi movie or a hardcore game, there can be this magnificent technological blend of the most heartfelt art and the most sophisticated science, that intensely inspires you to absorb as much as you can about this marvelous world and be the most competent you can possibly be to create the most extraordinary creation you can ever imagine.

Apart from the beautiful virtual world that stands to emerge from your own mind with code, game dev is packed with a vast array of mathematical concepts, ranging from basic arithmetic to complex calculations in 3D space.

Game development demands unparalleled logical reasoning due to its complex system of interactions. We create comprehensive rules, anticipate design loopholes, safeguard player balance and progression, and sometimes much more. This rigorous logical exercise stimulates the mind and significantly enhances decision-making skills and logical competence.

Final thoughts

The summary of it all is challenge, novelty, and creativity.

Embrace programming as a journey of continuous learning and intellectual discovery, rather than a predictable routine task. Find joy in the art of coding, make room for personal creativity, and imbue your craft with your unique touch. Implement new algorithms, solve word problems, create your own games and ensure the process matters as much as the result. Stay passionately curious, break the routine, and enjoy the thrill of innovation.

Every amazing new feature in GPT-4 Turbo

Great news – OpenAI just released GPT-4 Turbo, an upgraded version of the GPT-4 model with a context window up to 128K tokens – more than 300 pages of text, and a fourfold increase in regular GPT-4’s most powerful 32K context model.

The company made this known at its first-ever developer conference, touting a preview version of the model and promising a production-grade GPT-4 Turbo in the next few weeks.

Users will be able to have longer, more complex conversations with GPT-4 Turbo as there’ll be more room to remember more of what was said earlier in the chat.

DALLE-3 prompt: “A beautiful city with buildings made of different, bright, colorful candies and looks like a wondrous candy land”.
DALLE-3 prompt: “A beautiful city with buildings made of different, bright, colorful candies and looks like a wondrous candy land”

Also exciting to hear, GPT-4 Turbo is now trained on real-world knowledge and events up to April 2023, allowing us to build greater apps utilizing up-to-date data, without needing to manually keep it in the loop with custom data from embeddings and few-shot prompting.

Even better, the greater speed and efficiency of this new turbocharged model have made input tokens 3 times cheaper and slashed the cost of output tokens in half.

So, upgraded in capability, upgraded in knowledge, upgraded in speed, all with a fraction of the previous cost. That’s GPT-4 Turbo.

An innovative feature currently in preview, you can now pass image inputs to the GPT-4 model for processing, making it possible to perform tasks like generating captions, analyzing and classifying real-world images, and automated image moderation.

Then there’s the new DALL-E 3 API for automatically generating high-quality images and designs, and an advanced Text-to-speech (TTS) API capable of generating human-level speech with a variety of voices to choose from.

DALLE-3 outclasses Midjourney! Especially when it comes to creating complex images from highly detailed and creative prompts.

DALLE-3 (top) vs Midjourney (bottom). Prompt: "A vast landscape made entirely of various meats spreads out before the viewer. tender, succulent hills of roast beef, chicken drumstick trees, bacon rivers, and ham boulders create a surreal, yet appetizing scene. the sky is adorned with pepperoni sun and salami clouds".
DALLE-3 (top) vs Midjourney (bottom). Prompt: “A vast landscape made entirely of various meats spreads out before the viewer. tender, succulent hills of roast beef, chicken drumstick trees, bacon rivers, and ham boulders create a surreal, yet appetizing scene. the sky is adorned with pepperoni sun and salami clouds”. Source: DALL-E 3 vs. Midjourney: A Side by Side Quality Comparison

And we can’t forget the ambitious new Assistants API, aimed at helping devs build heavily customized AI agents with specific instructions that leverage extra knowledge and call models and tools to perform highly specialized tasks.

It’s always awesome to see these ground-breaking improvements in the world of AI, surely we can expect developers to take full advantage of these and produce even more intelligent and world-changing apps that improve the quality of life for everyone.

Create React App alternative: 5 times leaner, 0 vulnerabilities

Create React App is gone.

Yes, it’s no longer the recommended tool for starting React apps. Some of us were surprised, but many of us knew it was a long time coming.

There are much faster and leaner alternatives available today.

Why shouldn’t you use Create React App anymore?

Create React App was great.

Setting up React apps become as easy as ever without any need to bother with tedious Webpack and Babel configs. But despite the convenience, it had notable issues we just couldn’t ignore:

1. Bulky and slow

Create React App is a bloated tool.

It installs an unbelievable number of packages and takes forever to finish. If you’re a long-time CRA user like me, you’ve probably gotten used to this process, but it’s far from normal.

Once you enter the command to start a new project, you will quickly realize the insane number of packages CRA needs to set up.

Look at how large the node_modules folder is for CRA compared to Vite, a superior alternative. And both folders were just newly set up.

Many packages mean longer install times, even if you’re using PNPM or Yarn 2.

2. Insecure and outdated packages

Bundling so many packages makes it harder to keep track of them all, and many become abandoned and insecure later.

Things improved but any insecure package is something to worry about, and there are still quite a few.

Create React App has 8 high security vulnerabilities.
8 high security vulnerabilities! And that’s even better than what it used to be.

In my GitHub repos using CRA, I get several Dependabot alerts at every turn – even from one of my first GitHub projects.

And they’re almost always about security flaws in packages CRA installed.

Security weaknesses found in npm packages installed during Create React App setup.

3. Limited customization options

Create React App is a black box.

To add new libraries and customize it you usually have to depend on libraries like Craco – heck, before CRA version 5, Craco was pretty much our only way to get Tailwind CSS support.

I remember my earlier days of creating various Electron + React desktop apps, where I had to install react-app-rewired just to set the target to 'electron-renderer' to access Node modules in the React part of the app.

For total control, you can always do npm eject, but of course, then you become responsible for all the dependencies installed by CRA, which has trade-offs that may not be worth it for you.

What to use instead of Create React App

Old habits die hard, sure.

If you’ve been hesitating to switch to a superior tool, hopefully, the React team dumping CRA should finally do it for you. There are far more modern and better-maintained alternatives out there; let’s check out some of the most popular ones:

Vite

It’s a fast way to build modern web projects, designed for efficiency and smoother development.

The VueJS team is responsible for developing and maintaining Vite, so you can trust it’s a reliable tool.

  • “Extremely fast” Hot Module Replacement (HMR).
  • Out-of-the-box support for TypeScript.
  • A lightning-fast SWC compiler.

HMR means that code changes show instantly in the browser, avoiding full-page refreshes and saving valuable time.

It provides just about everything CRA does – but better and without the bloat. And it’s constantly being updated.

Vite logo

Here you don’t worry about outdated dependencies or vulnerable packages:

The Vite project has zero vulnerabilities.
Zero vulnerabilities.

Parcel

It’s a zero-config bundler with no setup required, yet it’s still more flexible than Create React App.

Parcel supports all the latest web technologies, including React, Vue, and TypeScript, and it can bundle any type of asset, from images and CSS to HTML and JavaScript.

Plus, it has excellent performance, thanks to its use of worker threads, which allows it to parallelize tasks and speed up builds.

Parcel logo

Parcel also has HMR support and is updated often with the latest features and optimizations like Vite. You can trust the team to keep up with the latest web technologies and best practices.

And what’s the result: your projects are built with the best tools available.

Custom Webpack configuration

Webpack is still king if you know what you’re doing.

The level of control of flexibility it gives you is unmatched as there are a massive number of options you can set, far more than just path and filename.

Another benefit to note: Webpack can generate a development build without a local server, unlike Parcel and others.

This particular feature saved me when I was debugging React in a VSCode extension. Loading data from localhost didn’t work properly for a Webview; I had to build to a local file and read the contents.

With source maps enabled and dev mode turned on, I could easily see the exact line where the error occurred in the TS code. Unlike with Parcel, where all I got was a minified build.

Of course, setting up a Webpack config can be complex and more involved – which is why CRA came around in the first place. You must configure various options to get everything working perfectly.

But once you’re set-up, you’ll have a heavily personalized build process for your project. It’s a trade-off that may or may not be worth it for you.

Frameworks to replace Create React App

Need React’s state and UI capabilities without extras? The tools we’ve seen are great.

Want high-performance apps with advanced features like SSR? Then consider frameworks with built-in routing, data fetching, etc.

Frameworks like:

Next.js

Many of us already know Next.js with advanced features like server-side rendering and static website generation.

Server-side rendering renders pages on the server and sends them to the client, greatly boosting performance, user experience, and SEO scores.

With intuitive file-based routing and built-in image optimization, Next.js is a smart option for building high-flying web apps with speed.

Remix

Another SSR-enable framework with powerful data features like:

  • Intelligent network error handling
  • Parallel fetching of CSS, JavaScript, and other assets to save time
  • Automatically ensuring the UI data is in sync with the server data after a user action.

One stand-out Remix feature is nested routes, which makes it possible to associate a specific section of a page with a particular route segment so that only that section updates when the route segment changes. This accelerates page transitions as no time is wasted re-rendering the entire page.

Gatsby

Gatsby is another awesome one – for scalable & fast-loading static sites.

There are a variety of starter kits and plugins to easily extend your site’s functionality and build a blog, e-commerce site, or portfolio site in no time at all.

With Gatsby, it’s effortless to power up your app with data from a CMS, especially with the GraphQL data layer for rapid integration with various APIs and services.

Final thoughts

CRA was a popular tool.

But it had its issues. It was bulky, outdated, and had limited customization.

Adaptation is crucial in the dynamic landscape of web development. By embracing modern tools, such as Vite, and Parcel, and in-depth frameworks like Next.js, Remix, and Gatsby, we can build faster and more efficient React apps.

7 amazing new JavaScript features in ES14 (ES2023)

The world’s most popular programming language just got a new update.

Ever since 2015, a new JavaScript version has come out every year with tons of powerful features to make life much easier, and 2023 has been no different. Let’s look at what ES14 has to offer.

1. Array toSorted() method

Sweet syntactic sugar.

ES14 comes with a new toSorted() method that makes it easier to sort an array and return a copy without mutation.

So instead of doing this:

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

We now get to do this:

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

And just like sort(), toSorted() takes a callback function that lets you decide how the sort should happen – ascending or descending, alphabetical or numeric.

JavaScript
const nums = [5, 2, 6, 3, 1, 7, 4]; const sorted = nums.toSorted((a, b) => b - a); console.log(sorted); // [7, 6, 5, 4, 3, 2, 1]

2. Hashbang grammar

What’s a hashbang?

Basically, it’s a sequence of characters in a file that tells the Linux Shell what interpreter or engine it should use to run the file. For example:

codingbeauty.sh
#!/bin/bash echo "How are you all doing today?"

With this hashbang, we can now easily run codingbeauty.sh directly in the shell, after using the chmod command to make the script executable.

Hashbangs also let us hide all the juicy implementation details in our scripts and indicate specific interpreter versions to execute the script files.

So with ES14, we can now do this effortlessly in JavaScript files, like this:

codingbeauty.js
#!/usr/bin/env node console.log("I'm doing great, how are you?");

3. Array toSpliced() method

Those immutability purists out there will no doubt be pleased with all these new Array methods.

toSorted() is to sort() as toSpliced() is to splice():

JavaScript
const colors = ['red', 'green', 'blue', 'yellow', 'pink']; const spliced = colors.toSpliced(1, 2, 'gray', 'white'); console.log(spliced); // [ 'red', 'gray', 'white', 'yellow', 'pink' ] // Original not modified console.log(colors); // ['red', 'green', 'blue', 'yellow', 'pink'];

Here toSpliced() removes 2 of the array elements, starting from index 1, and inserts 'gray' and 'white' in their place.

4. Symbols as WeakMap keys

WeakMaps; not very popular, are they?

Well, they’re a lot like Maps, except their keys can only contain non-primitive objects – no strings or numbers allowed here. These keys are stored as weak references, meaning the JavaScript engine can carry out garbage collection on the objects when it needs to if there is no other reference to the objects in memory apart from the keys.

One powerful use of WeakMaps is custom caching: by using objects as keys, you can associate cached values with specific objects. When the objects are garbage collected, the corresponding WeakMap entries are automatically removed, clearing the cache immediately.

JavaScript
const map = new Map(); const weakMap = new WeakMap(); const obj1 = { name: 'React' }; const obj2 = { name: 'Angular' }; map.set(obj1, 'Value for obj1 at Coding Beauty'); weakMap.set(obj2, 'Value for obj2 at Coding Beauty'); console.log(map.get(obj1)); // Output: Value for obj1 console.log(weakMap.get(obj2)); // Output: Value for obj2 obj1 = null; obj2 = null; console.log(map.get(obj1)); // Output: Value for obj1 console.log(weakMap.get(obj2)); // Output: undefined (obj2 has been garbage collected)

So ES14 makes it possible to define JavaScript Symbols as keys. This can make the role a key-value pair plays in a WeakMap clearer.

JavaScript
let mySymbol = Symbol('mySymbol'); let myWeakMap = new WeakMap(); let obj = { name: 'Coding Beauty' }; myWeakMap.set(mySymbol, obj); console.log(myWeakMap.get(mySymbol)); // Output: object

And what are they meant for?

5. Array toReversed() method

Another new Array method to promote immutability and functional programming.

The name’s self-explanatory: give me the reversed version of my array.

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:

JavaScript
const arr = [5, 4, 3, 2, 1] const reversed = arr.toReversed(); console.log(reversed); // [5, 4, 3, 2, 1] // Original NOT modified console.log(arr); // [1, 2, 3, 4, 5]

6. Array find from last

Sure we can already use the Array find() method to find an element in an array that passes a specified test condition. And findIndex() will give us the index of such an element.

But find() and findIndex() both start searching from the first element of the array. What if it’ll be better for us to search from the last element instead?

Here we’re trying to get the item in the array with the value prop equal to y. With find() and findIndex():

JavaScript
const letters = [ { value: 'v' }, { value: 'w' }, { value: 'x' }, { value: 'y' }, { value: 'z' }, ]; const found = letters.find((item) => item.value === 'y'); const foundIndex = letters.findIndex((item) => item.value === 'y'); console.log(found); // { value: 'y' } console.log(foundIndex); // 3

This works, but as the target object is closer to the tail of the array, we could make this program run faster if we use the new ES2022 findLast() and findLastIndex() methods to search the array from the end.

JavaScript
const letters = [ { value: 'v' }, { value: 'w' }, { value: 'x' }, { value: 'y' }, { value: 'z' }, ]; const found = letters.findLast((item) => item.value === 'y'); const foundIndex = letters.findLastIndex((item) => item.value === 'y'); console.log(found); // { value: 'y' } console.log(foundIndex); // 3

Another use case might require that we specifically search the array from the end to get the correct item. For example, if we want to find the last even number in a list of numbers, find() and findIndex() would produce a totally wrong result.

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

Yes, we could call the reverse() method on the array to reverse the order of the elements before calling find() and findIndex().

But this approach would cause unnecessary mutation of the array, as reverse() reverses the elements of an array in place. The only way to avoid this mutation would be to make a new copy of the entire array, which could cause performance problems for large arrays.

And this beside the fact that findIndex() would still not work on the reversed array, as reversing the elements would also mean changing the indexes they had in the original array. To get the original index, we would need to perform an additional calculation, which means writing more code.

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

It’s in cases like where the findLast() and findLastIndex() methods come in handy.

JavaScript
const nums = [7, 14, 3, 8, 10, 9]; const lastEven = nums.findLast((num) => num % 2 === 0); 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.

7. Array with() method

Unlike the others, with() has no complementary mutating method. But once you see it in action, you’ll know that it’s the immutable approach to changing a single element.

In so many languages, you typically modify a single array element like this:

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

But see what we can do now, in ES2023 JavaScript:

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

This year was all about easier functional programming and immutability.

Indeed, the reliability and consistency that immutability brings cannot be overstated. With the rise of declarative frameworks like React and Vue as well as Redux and other libraries, we’ve seen immutable JavaScript array methods explode in popularity; it’s only natural that we see more and more of them come baked into the language as it matures.

They stole the show in 2023, just like JavaScript took over a huge chunk of the language ecosystem a while back, with many millions of developers keeping that fire burning today. Let’s see what the future holds.

One-liners: far more than just one line

Here’s one:

JavaScript
const groupBy = (arr, groupFn) => arr.reduce( (grouped, obj) => ({ ...grouped, [groupFn(obj)]: [...(grouped[groupFn(obj)] || []), obj], }), {} );

and another:

JavaScript
const randomHexColor = () => `#${Math.random().toString(16).slice(2, 8).padEnd(6, '0')}`; console.log(randomHexColor()); // #7a10ba (varies) console.log(randomHexColor()); // #65abdc (varies)

Indeed, they are impressive when done right; a nice way to show off language mastery.

But exactly is a one-liner? Is it really code that takes only one line? If so, then can’t every piece of code qualify as a one-liner, if we just remove all the newline characters? Think about it.

A minified JavaScript file with only one line of code.

It seems like we need a more rigorous definition of what qualifies as a one-liner. And, after a few minutes of thought when writing a previous article on one-liners, I came up with this:

A one-liner is a code solution to a problem, implemented with a single statement in a particular programming language, optionally using only first-party utilities.

Tari Ibaba (😎)

You can clearly see those particular keywords that set this definition apart from others

1. “…single statement…”

Single line or single statement? I go with the later.

Because the thing is, we squeeze every program ever made in a single line of code if we wanted; all the whitespace and file separation is only for us and our fellow developers.

If you’ve used Uglify.js or a similar minifier, you know what it does to all those pitiful lines of code; why it’s called Uglify.

Uglify changes this:

JavaScript
/** Obviously redudant comments here. Just meant to emphasize what Uglify does Class Person */ class Person { /** Constructor */ constructor(name, age) { this.name = name; this.age = age; } /** Print Message */ printMessage() { console.log(`Hello! My name is ${this.name} and I am ${this.age} years old.`); } } /** Creating Object */ var person = new Person('John Doe', 25); /** Printing Message */ person.printMessage();

to this:

JavaScript
class Person{constructor(e,n){this.name=e,this.age=n}printMessage(){console.log(`Hello! My name is ${this.name} and I am ${this.age} years old.`)}}var person=new Person("John Doe",25);person.printMessage();

Would you be impressed by someone who actually wrote code like in this minified way? I would say it’s just badly formatted code.

Would you call this a one-liner?

JavaScript
const sum = (a, b) => { const s1 = a * a; const s2 = b * b; return s1 + s2; }

Tools like the VS Code Prettier extension will easily split up those 3 statements into multiple lines:

The three statements are separated after VS Code Prettier auto-format on save.
The three statements are separated after VS Code Prettier auto-format on save.

A true one-liner way to get the sum of two squares would be something like this:

JavaScript
const sum = (a, b) => a * a + b * b;

One short, succinct statement does the same job with equal clarity.

On the other hand, what about code that spans multiple lines but only uses one statement? Like this:

JavaScript
const capitalizeWithoutSpaces = (str) => str .split('') .filter((char) => char.trim()) .map((char) => char.toUpperCase()) .join('');

I would say this function’s body is far more qualified to be a one-line than the two examples we saw above; single statement.

“…particular programming language”

We need this part because of abstraction.

int sum(int a, int b) { return a * a + b * b; }

This is a one-liner, isn’t it? Very harmless-looking and easy to understand.

How about now? :

sum(int, int): push rbp mov rbp, rsp mov DWORD PTR [rbp-4], edi mov DWORD PTR [rbp-8], esi mov eax, DWORD PTR [rbp-4] imul eax, eax mov edx, eax mov eax, DWORD PTR [rbp-8] imul eax, eax add eax, edx pop rbp ret

Wait, this is just another piece of code, right? Well, it is, except that they do exactly the same thing, but in different languages. One in C++, and the other in Assembly.

Now imagine how much more an equivalent machine language program will have. Clearly, we can only our sum() a one-liner function in the context of C++.

“…using only first-party utilities”

Once again, we need this part because of abstraction.

For us to consider a piece of code as a one-liner, it should only use built-in functions and methods that are part of the language’s standard library or core functionality. For example: array methods, the http module in Node.js, the os module in Python, and so on.

Without this, capitalizeWithoutSpaces() below would easily pass as a JavaScript one-liner:

JavaScript
// Not a one-liner const capitalizeWithoutSpaces = (str) => filter(str.split(''), (char) => char.trim()) .map((char) => char.toUpperCase()) .join(''); function filter(arr, callback) { // Look at all these lines const result = []; for (const item of arr) { if (callback(item)) { result.push(item); } } return result; }

filter could have contained many thousands of lines, yet capitalizeWithoutSpaces would still be given one-liner status.

It’s kind of controversial because a lot of these so-called first-party utilities are abstractions themselves with logic spanning dozens of lines. But just like the “single statement” specifier, it makes it impossible to have an unlimited number of one-liners.

Final thoughts

The essence of a one-liner in programming extends beyond the literal interpretation of its name. It lies not only in the minimalism of physical lines but also in the elegance and sophistication of its execution. It often requires a sound comprehension of the language at hand, an ability to concisely solve a problem, and the art of utilizing the language’s core functionalities with finesse.

A one-liner isn’t merely about squeezing code into a single line; It is where the clarity of thought, the elegance of language mastery, and the succinctness of execution converge. It’s the realm where brevity meets brilliance and the art of coding truly shines.

Mojo: 7 brilliant Python upgrades in the new AI language

It is 35,000 times faster than Python. It is quicker than C. It is as easy as Python.

Enter Mojo: a newly released programming language made for AI developers and made by Modular, a company founded by Chris Lattner, the original creator of Swift.

This 35000x claim came from a benchmark comparison between Mojo and other languages, using the Mandelbrot algorithm on a particular AWS instance.
This 35000x claim came from a benchmark comparison between Mojo and other languages, using the Mandelbrot algorithm on a particular AWS instance.

It’s a superset of Python, combining Python’s usability, simplicity, and versatility with C’s incredible performance.

If you’re passionate about AI and already have a grasp on Python, then Mojo is definitely worth a try. So, let’s dive in and explore 7 powerful features of this exciting language together.

Mojo’s features

I signed up for Mojo access shortly after it was announced and got access a few days later.

I got access to the Mojo playground.

I started exploring all the cool new features they had to offer and even had the chance to run some code and see the language in action. Here are 7 interesting Python upgrades I found:

1. let and var declarations

Mojo introduces new let and var statements that let us create variables.

If we like we can specify a type like Int or String for the variable, as we do in TypeScript. var allows variables to change; let doesn’t. So it’s not like JavaScript’s let and var – There’s no hoisting for var and let is constant.

Mojo
def your_function(a, b): let c = a # Uncomment to see an error: # c = b # error: c is immutable if c != b: let d = b print(d) your_function(2, 3)

2. structs for faster abstraction

We have them in C++, Go, and more.

Structs are a Mojo feature similar to Python classes, but they’re different because Mojo classes are static: you can’t add more methods are runtime. This is a trade-off, as it’s less flexible, but faster.

Mojo
struct MyPair: var first: Int var second: Int # We use 'fn' instead of 'def' here - we'll explain that soon fn __init__(inout self, first: Int, second: Int): self.first = first self.second = second fn __lt__(self, rhs: MyPair) -> Bool: return self.first < rhs.first or (self.first == rhs.first and self.second < rhs.second)

Here’s one way struct is stricter than class: all fields must be explicitly defined:

Fields must be explicitly defined in Mojo structs.

3. Strong type checking

These structs don’t just give us flexibility, they let us check variable types at compile-time in Mojo, like the TypeScript compiler does.

Mojo
def pairTest() -> Bool: let p = MyPair(1, 2) # Uncomment to see an error: # return p < 4 # gives a compile time error return True

The 4 is an Int, the p is a MyPair; Mojo simply can’t allow this comparison.

4. Method overloading

C++, Java, Swift, etc. have these.

Function overloading is when there are multiple functions with the same name that accept parameters with different data types.

Look at this:

Mojo
struct Complex: var re: F32 var im: F32 fn __init__(inout self, x: F32): """Makes a complex number from a real number.""" self.re = x self.im = 0.0 fn __init__(inout self, r: F32, i: F32): """Makes a complex number from its real and imaginary parts.""" self.re = r self.im = i

Typeless languages like JavaScript and Python simply can’t have function overloads, for obvious reasons.

Although overloading is allowed in module/file functions and class methods based on parameter/type, it won’t work based on return type alone, and your function arguments need to have types. If don’t do this, overloading won’t work; all that’ll happen is the most recently defined function will overwrite all those previously defined functions with the same name.

5. Easy integration with Python modules

Having seamless Python support is Mojo’s biggest selling point by far.

And using Python modules in Mojo is straightforward. As a superset, all you need to do is call the Python.import_module() method, with the module name.

Here I’m importing numpy, one of the most popular Python libraries in the world.

Mojo
from PythonInterface import Python # Think of this as `import numpy as np` in Python let np = Python.import_module("numpy") # Now it's like you're using numpy in Python array = np.array([1, 2, 3]) print(array)

You can do the same for any Python module; the one limitation is that you have to import the whole module to access individual members.

All the Python modules will run 35,000 times faster in Mojo.

6. fn definitions

fn is basically def with stricter rules.

def is flexible, mutable, Python-friendly; fn is constant, stable, and Python-enriching. It’s like JavaScript’s strict mode, but just for def.

Mojo
struct MyPair: fn __init__(inout self, first: Int, second: Int): self.first = first self.second = second

fn‘s rules:

  • Immutable arguments: Arguments are immutable by default – including self – so you can’t mistakenly mutate them.
  • Required argument types: You have to specify types for its arguments.
  • Required variable declarations: You must declare local variables in the fn before using them (with let and var of course).
  • Explicit exception declaration: If the fn throws exceptions, you must explicitly indicate so – like we do in Java with the throws keyword.

7. Mutable and immutable function arguments

Pass-by-value vs pass-by-reference.

You may have across this concept in languages like C++.

Python’s def function uses pass-by-reference, just like in JavaScript; you can mutate objects passed as arguments inside the def. But Mojo’s def uses pass-by-value, so what you get inside a def is a copy of the passed object. So you can mutate that copy all you want; the changes won’t affect the main object.

Pass-by-reference improves memory efficiency as we don’t have to make a copy of the object for the function.

But what about the new fn function? Like Python’s def, it uses pass-by-reference by default, but a key difference is that those references are immutable. So we can read the original object in the function, but we can’t mutate it.

Immutable arguments

borrowed a fresh, new, redundant keyword in Mojo.

Because what borrowed does is to make arguments in a Mojo fn function immutable – which they are by default. This is invaluable when dealing with objects that take up a substantial amount of memory, or we’re not allowed to make a copy of the object we’re passing.

For example:

Mojo
fn use_something_big(borrowed a: SomethingBig, b: SomethingBig): """'a' and 'b' are both immutable, because 'borrowed' is the default.""" a.print_id() // 10 b.print_id() // 20 let a = SomethingBig(10) let b = SomethingBig(20) use_something_big(a, b)

Instead of making a copy of the huge SomethingBig object in the fn function, we simply pass a reference as an immutable argument.

Mutable arguments

If we want mutable arguments instead, we’ll use the new inout keyword instead:

Mojo
struct Car: var id_number: Int var color: String fn __init__(inout self, id: Int): self.id_number = id self.color = 'none' # self is passed by-reference for mutation as described above. fn set_color(inout self, color: String): self.color = color # Arguments like self are passed as borrowed by default. fn print_id(self): # Same as: fn print_id(borrowed self): print('Id: {0}, color: {1}') car = Car(11) car.set_color('red') # No error

self is immutable in fn functions, so we here we needed inout to modify the color field in set_color.

Key takeaways

  • Mojo: is a new AI programming language that has the speed of C, and the simplicity of Python.
  • let and var declarations: Mojo introduces let and var statements for creating optionally typed variables. var variables are mutable, let variables are not.
  • Structs: Mojo features static structs, similar to Python classes but faster due to their immutability.
  • Strong type checking: Mojo supports compile-time type checking, akin to TypeScript.
  • Method overloading: Mojo allows function overloading, where functions with the same name can accept different data types.
  • Python module integration: Mojo offers seamless Python support, running Python modules significantly faster.
  • fn definitions: The fn keyword in Mojo is a stricter version of Python’s def, requiring immutable arguments and explicit exception declaration.
  • Mutable and immutable arguments: Mojo introduces mutable (inout) and immutable (borrowed) function arguments.

Final thoughts

As we witness the unveiling of Mojo, it’s intriguing to think how this new AI-focused language might revolutionize the programming realm. Bridging the performance gap with the ease-of-use Python offers, and introducing powerful features like strong type checking, might herald a new era in AI development. Let’s embrace this shift with curiosity and eagerness to exploit the full potential of Mojo.