If you’re sprinkling arrow functions everywhere like confetti, you might be part of the overengineering problem.
The Arrow Function Obsession
Arrow functions (()=>
) are concise, trendy, and make you feel like a 10x developer. But when you start using them for everything, you’re not writing clean code—you’re writing cryptic code.
Example:
const add = (a, b) => a + b;
Sure, it’s short. But is it clear?
Now consider:
function add(a, b) {
return a + b;
}
This version is explicit and easier to read, especially for those new to the codebase.
The .then()
Tunnel Vision
Promises were a game-changer. But chaining .then()
calls like it’s some JavaScript flex doesn’t make you clever—it makes your code harder to trace.
Example:
doSomething()
.then(result => doSomethingElse(result))
.then(newResult => doFinalThing(newResult))
.catch(error => handleError(error));
Now try:
async function execute() {
try {
const result = await doSomething();
const newResult = await doSomethingElse(result);
await doFinalThing(newResult);
} catch (error) {
handleError(error);
}
}
It reads top to bottom like a normal person would. Async/await is cleaner, clearer, and doesn’t need a flowchart.
map()
Abuse
Using map()
when you're not even using the returned array? That’s not functional—it’s just wrong.
Example:
items.map(item => doSomething(item));
No return value used, so map()
is just showing off. Use forEach()
instead:
items.forEach(item => doSomething(item));
Clarity wins over functional cosplay.
this
vs that.bind(this)
You know what's uglier than this
in JavaScript? Having to slap that.bind(this)
on your methods just to make the context behave.
Example:
class Greeter {
constructor(name) {
this.name = name;
this.sayHello = this.sayHello.bind(this);
}
sayHello() {
console.log(`Hello, ${this.name}`);
}
}
Versus just using an arrow function when it makes sense:
class Greeter {
constructor(name) {
this.name = name;
}
sayHello = () => {
console.log(`Hello, ${this.name}`);
}
}
Stop fighting the language—write what works cleanly.
Overengineering in Disguise
Overengineering often masquerades as “best practices.” You might think you're writing clean code, but in reality, you're adding unnecessary complexity.
Examples include:
- Using arrow functions everywhere, even when regular functions would be clearer.
- Creating layers of abstraction that serve no real purpose.
- Wrapping everything in
Promise.then()
chains instead of just usingasync/await
. - Using
map()
when you should be usingforEach()
because you’re not even using the return value. - Binding methods like it’s 2010 when arrow functions solve it better.
These practices can lead to code that's harder to understand, debug, and maintain.
Pragmatism Over Perfection
Being a pragmatic programmer means prioritizing clarity and simplicity over cleverness.
Ask yourself:
- Is this code easy to read and understand?
- Does this abstraction add value, or is it just adding ceremony?
- Am I solving a real problem, or just flexing with syntax?
Remember: you’re not getting paid per line of code or for impressing other devs with how “advanced” your patterns are. You get paid when the thing works, and it keeps working.
Conclusion
Choose the right tool for the job.
Use arrow functions when they make the code cleaner and more concise.
Use regular functions when clarity and readability are paramount.
Use async/await instead of chaining promises just to look functional.
Use forEach()
when you’re not returning anything.
Avoid .bind(this)
when arrow functions do it better.
Avoid the trap of overengineering. Simplicity wins.
Because at the end of the day, code is read more often than it's written.
Top comments (0)