How I Refactored My TypeScript Backend to Fully Follow Airbnb ESLint Rules (With Real Before & After Examples)
When I started enforcing Airbnb ESLint rules in my TypeScript backend project, I quickly realized how many common coding patterns violate them.
At first, the errors felt annoying. But once I understood the reasoning behind each rule, my code became:
- more predictable
- safer
- more functional
- more consistent
- easier to maintain
In this article, I’ll show real examples from my project, including:
- ✅ why the rule exists
- ✅ what causes the ESLint error
- ✅ how to fix it properly
- ✅ before vs after code
1. Avoid return await in async functions
❌ Problem
Returning an awaited value that is already a promise.
return await repository.save(user);
Why ESLint complains
Async functions already wrap return values in a Promise.
return await only adds unnecessary microtask delay.
✅ Fix
return repository.save(user);
Cleaner and faster.
2. No isNaN() — Use Number.isNaN()
❌ Problem
if (isNaN(Date.parse(value))) { ... }
Why
Global isNaN() coerces values → unreliable.
✅ Fix
if (Number.isNaN(Date.parse(value))) { ... }
No implicit conversion.
3. No Parameter Reassignment
Airbnb forbids modifying function parameters.
❌ Problem
item.total_hours = 0;
arr[index] = arr[lastIndex];
timezone.timezone = 'UTC';
Why
Mutating parameters causes hidden side effects.
✅ Fix — clone first
const updatedItem = {
...item,
total_hours: 0,
};
Example: swap array safely
const newArr = [...arr];
[newArr[i], newArr[j]] = [newArr[j], newArr[i]];
return newArr;
4. No await inside loops
❌ Problem
for (const item of items) {
await save(item);
}
Runs sequentially → slow.
✅ Parallel execution
await Promise.all(items.map(save));
Much faster.
5. No for...of or for...in
Airbnb prefers array iteration helpers.
❌ for...of
for (const task of tasks) {
process(task);
}
✅ forEach
tasks.forEach(process);
6. Object iteration — NEVER use for...in
❌ Problem
for (const key in obj) {
values.push(obj[key]);
}
Iterates prototype chain.
✅ Correct
Object.entries(obj).forEach(([key, value]) => {
values.push(value);
});
Safe and explicit.
7. forEach vs map vs for...of (VERY IMPORTANT)
Many developers treat these as interchangeable — they are NOT.
Looping arrays synchronously
These behave similarly:
for...of
forEach
Both just iterate.
map() is different
It creates a NEW array.
const doubled = numbers.map(n => n * 2);
If you ignore the return → misuse.
Async iteration differences
❌ Wrong
items.forEach(async item => {
await save(item);
});
forEach does NOT wait.
❌ Also wrong
items.map(async item => {
await save(item);
});
Promises created but ignored.
✅ Correct parallel async
await Promise.all(items.map(save));
8. Object iteration example (real fix)
❌ Before
for (const key in percentage) {
if (!ignoredKeys.has(key)) {
values.push(percentage[key]);
}
}
✅ After
Object.entries(percentage).forEach(([key, value]) => {
if (!ignoredKeys.has(key) && typeof value === 'number') {
values.push(value);
}
});
9. Replace loops pushing promises
❌ Before
for (const assignee of assignees) {
promises.push(deleteTask(assignee));
}
✅ After
const promises = assignees.map(deleteTask);
await Promise.all(promises);
Cleaner and faster.
10. Remove useless constructors
❌ Before
constructor() {
super();
}
✅ After
Delete it.
11. No unused expressions
❌ Problem
status.length > 0 && status.forEach(...)
Expression result ignored.
✅ Fix
if (status.length > 0) {
status.forEach(...)
}
12. Async functions must always return
❌ Problem
async function remove() {
try {
return await repo.delete();
} catch (e) {
console.error(e);
}
}
Missing return path.
✅ Fix
catch (e) {
console.error(e);
return null;
}
13. Avoid variable shadowing
❌ Problem
const sum = Object.keys(values).reduce((acc, key) => ...
key already declared earlier.
✅ Fix
.reduce((acc, innerKey) => ...
14. Consistent return values
Every async path must return something.
Final Mental Model for Airbnb Style
- ✔ Prefer functional iteration
- ✔ Avoid mutation
- ✔ Avoid sequential async loops
- ✔ Explicit returns
- ✔ Safe object iteration
- ✔ Pure functions
Quick Cheat Sheet
| Goal | Use |
|---|---|
| iterate array | forEach |
| transform array | map |
| async parallel | Promise.all(map()) |
| object iteration | Object.entries |
| update data | clone + return |
| async function | always return |
Final Thoughts
Airbnb ESLint rules are strict — but they force you to write:
- ✔ predictable
- ✔ side-effect free
- ✔ scalable
- ✔ high performance
- ✔ production-grade code
Once you adapt to the mindset, your code quality improves dramatically.
If you found this helpful, next you should learn:
- ✔ sequential vs parallel async performance
- ✔ functional programming in TypeScript
- ✔ immutability patterns
- ✔ Promise concurrency control
Happy coding 🚀