I've been using JavaScript daily for 7 years, and I'm not able to remember the syntax of a JavaScript for loop.
Despite this fact, I'm a rather successful freelance developer. Recently I even had the awesome opportunity to work for Facebook, as the Docusaurus lead maintainer, writing the code for the framework that powers the documentation sites of Babel, Prettier, Jest, ReactNative...
I'll explain why I'm not able to remember such syntax, and why it does not matter much.
My story
TLDR: I'm a functional programmer
I've really started programming at the beginning of my engineer degree, around 2004 (before that, I was only able to hack some scripts for Counter-Strike console or IRC).
Most of our school teaching was based on Java, but we also saw a bit of C, C++, OCaml.
The first loop syntax I learned probably looked like this one:
List<Integer> numbers = Lists.newArrayList(1, 2, 3);
for (int i = 0; i < numbers.length; i++) {
System.out.println(numbers.get(i));
}
Before I came out of school, Java 6 brought some new, simpler syntax:
List<Integer> numbers = Lists.newArrayList(1, 2, 3);
for (Integer number : numbers) {
System.out.println(number);
}
At my first job, the Google Guava lib brought some new verbose functional syntax to Java, and I was able to do weird things with it 😅.
List<Integer> numbers = Lists.newArrayList(1, 2, 3);
Lists.newArrayList(Collections2.transform(numbers, new Function<Integer,Void>() {
@Override
public Void apply(Integer number) {
System.out.println(number);
return null;
}
}));
This Guava lib got me intrigued by functional programming, and lead me to become a Scala developer since 2012, and I was finally able to use functional programming concepts (loops, but not only) without the ugly Java/Guava syntax.
val numbers = List(1, 2, 3)
numbers.foreach(println)
In 2013, ReactJS came out, and this totally changed my career path. At this time, I didn't like JavaScript much and was only able to hack some inline JQuery things in server-rendered pages. But as a startup CTO, I saw my team struggle with architecture, BackboneJS and RequireJS, and thought I had to become better at frontend to lead them.
AngularJS looked like the safer choice at this time, but a Scala developer colleague really pushed for React, which looked fancy and risky. All things made sense with the visionary post of David Nolen (The Future of JavaScript MVC Frameworks), and we finally adopted React in January 2014, as it seemed we would be able to use our functional programming knowledge to the frontend app as well, and make the UI more predictable.
Fast forward, it wasn't easy to be a React early-adopter for our critical app. All companies were building their own state management solution, trying to figure things out, and so we did, based on the ideas of David Nolen to hold a single immutable state in an atom (I was able to get a hacky time-travel working before Redux).
Since then both the JavaScript language and the ReactJS ecosystem have progressed a lot, and it's very common to use functional programming principles nowadays.
Why I can't write a JavaScript for loop?
As a long-time functional programmer, I simply don't write for loops very often.
Like anything you don't use regularly, you end up forgetting the syntax.
Today, many of us use ES5+ syntax (or Lodash/Ramda...) and some functional constructs. Using map
, forEach
, filter
are the most illustrated examples in the JS community.
const numbers = [1, 2, 3]
numbers.forEach(number => console.log(number));
But we can go much further than that once we are more experienced with functional programming, and almost never write any for loops anymore.
Don't get me wrong, it's not necessarily a goal to not write for loops anymore, and I'm not telling you that you should remove all for loops of your production codebase.
Very often there's an alternative syntax possible for your loops that might be more expressive and easier to understand. After a while, you end up seeing a for loop as an implementation detail of a more elegant functional abstraction.
This more expressive syntax is not only for loops, and you can as well see a functional abstraction being an implementation detail of another higher-level abstraction.
Let's consider we want to increment the age of 2 brothers.
const brothers = {
id1: {name: "Sébastien", age: 34},
id2: {name: "Antoine", age: 23}
};
I very often see the array.reduce()
operator used when a more expressive alternative was possible.
function incrementBrothersAges() {
return Object.entries(brothers)
.reduce((acc,[id,brother]) => {
acc[id] = {...brother, age: brother.age + 1};
return acc;
},{})
}
You know what? I really struggled to write this code.
My first attempt was not working at all (TypeScript would have helped).
function incrementBrothersAges() {
return Object.entries(brothers)
// acc is the first arg
.reduce(([id,brother], acc) => {
acc[id] = {...brother, age: brother.age + 1};
// we must return the acc here
},{});
}
Yet, writing this kind of transform is idiomatic for me, using higher-level functional programming abstractions, such as mapValues
(included in lodash).
function incrementBrothersAges() {
return mapValues(
brothers,
brother => ({...brother, age: brother.age + 1})
);
}
And I think nobody would argue that this is harder to read and maintain right? If junior developers are not familiar with functional programming, they'll catch up fast and get used to it. This might even be harder to learn reduce
.
Why it does not matter?
I don't write for loops (or reduce
), but I know the concepts. I know that these loops exist in different syntaxes, that can be useful for different use cases, and how to make a choice with a good tradeoff (performance, readability...).
I'll illustrate this with a concrete example from my daily work that actually led me to write this article.
I had this async function that performs some long task for a given country.
async function runCountryTask(country) {
// Simulate a long async task (1 to 5 seconds)
const taskDuration = 1000 + Math.random() * 4000;
await new Promise(resolve => setTimeout(resolve, taskDuration));
console.log(`Task completed for ${country}`);
}
This task had to be run for many countries, but the tasks should be run sequentially, not in parallel.
As I know the concepts, and I knew that the following would not work, as Promise.all
would run all tasks in parallel.
async function runAllCountryTasks() {
const countries = ["FR", "EN", "US", "DE", "UK", "IT"];
// runs in parallel
await Promise.all(countries.map(runCountryTask))
}
I also knew that there were multiple possible solutions to solve this problem:
- use a third-party dependency exposing the higher-level async primitive I need
- use
Promise.then()
recursively - use async/await, using a for loop syntax to iterate over a fixed-size array
I didn't want to introduce a new third party dependency just for a tiny utility function.
I also knew that using Promise.then()
recursively could be harder to read, write, and maintain. There are many ways to write such a recursion, one of them could be:
async function forEachAsyncSequential(array, asyncFn) {
await array.reduce((acc, item) => {
return acc.then(() => asyncFn(item))
}, Promise.resolve());
}
So I opted for a basic for loop, as it seemed the right tradeoff.
As I'm totally unable to remember the syntax (in
vs of
, can I actually use const
?), I had to actually google it, and it didn't take me long to be able to write the TypeScript code that will be shipped in production.
export async function forEachAsyncSequencial<T>(
array: T[],
asyncFn: (t: T) => Promise<void>,
): Promise<void> {
for (const item of array) {
await asyncFn(item);
}
}
async function runAllCountryTasks() {
const countries = ["FR", "EN", "US", "DE", "UK", "IT"];
// runs in sequence
await forEachAsyncSequencial(countries, runCountryTask);
}
Believe me or not, but I think It's the only for loop I actually wrote in JavaScript this year. And once it's written, I won't need to write it ever again (at least for this project), as it's now part of my functional programming abstractions, that I can reuse anywhere I need.
Conclusion
It's not very important to remember every syntax details to be productive in your daily work, particularly when you don't use them often (on purpose), as you prefer to work with more expressive, higher-level abstractions.
I had to google many things to write this article:
- Syntax for declaring a Java list
- Syntax for iterating a Java list
- Does
System.out.println
accept an Integer? - Syntax for Scala string interpolation
- Is there a
forEach
in Guava (actually found my own StackOverflow question) - What are the possible syntaxes for iterating over a JavaScript array
- Signature of
array.reduce()
Not remembering all this does not matter much, as long as I know what to look for.
In the same way, I don't know much about many other JavaScript things:
- prototypes: I think I never hard to use them directly in my entire life, and I'm fine
- classes: used them temporarily when I really had to in React
- JavaScript quirks: I know some of them, but simply avoid the others by using ESLint,
===
, TypeScript... it's not worth knowing all of them - ...
The knowledge and concepts you learn are more easily transposable from one language to another. I was able to learn React and contribute to its ecosystem quickly, thanks to my functional programming background.
I would argue that knowing how to do a recursive algorithm is more important than knowing the syntax of a for loop of a particular language. You will likely write many recursive algorithms in your career: the concept of recursion is not going anywhere anytime soon. But it's way more likely that you switch from one language to another from time to time.
Hopefully, writing this post will help me remember the syntax for a while until I forget it again 🤪.
🙏 If you like this post, please like it, share it or comment it 🙏:
For more content like this, subscribe to my mailing list and follow me on Twitter.