← Back to postsCoding Notes
EnglishPublished Feb 24, 2026Updated Feb 24, 20263 min read

How I Refactored My TypeScript Backend to Fully Follow Airbnb ESLint Rules (With Real Before & After Examples)

Tips

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.

ts
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

ts
return repository.save(user);

Cleaner and faster.


2. No isNaN() — Use Number.isNaN()

❌ Problem

ts
if (isNaN(Date.parse(value))) { ... }

Why

Global isNaN() coerces values → unreliable.


✅ Fix

ts
if (Number.isNaN(Date.parse(value))) { ... }

No implicit conversion.


3. No Parameter Reassignment

Airbnb forbids modifying function parameters.

❌ Problem

ts
item.total_hours = 0;
arr[index] = arr[lastIndex];
timezone.timezone = 'UTC';

Why

Mutating parameters causes hidden side effects.


✅ Fix — clone first

ts
const updatedItem = {
    ...item,
    total_hours: 0,
};

Example: swap array safely

ts
const newArr = [...arr];
[newArr[i], newArr[j]] = [newArr[j], newArr[i]];
return newArr;

4. No await inside loops

❌ Problem

ts
for (const item of items) {
    await save(item);
}

Runs sequentially → slow.


✅ Parallel execution

ts
await Promise.all(items.map(save));

Much faster.


5. No for...of or for...in

Airbnb prefers array iteration helpers.


❌ for...of

ts
for (const task of tasks) {
    process(task);
}

✅ forEach

ts
tasks.forEach(process);

6. Object iteration — NEVER use for...in

❌ Problem

ts
for (const key in obj) {
    values.push(obj[key]);
}

Iterates prototype chain.


✅ Correct

ts
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:

ts
for...of
forEach

Both just iterate.


map() is different

It creates a NEW array.

ts
const doubled = numbers.map(n => n * 2);

If you ignore the return → misuse.


Async iteration differences

❌ Wrong

ts
items.forEach(async item => {
    await save(item);
});

forEach does NOT wait.


❌ Also wrong

ts
items.map(async item => {
    await save(item);
});

Promises created but ignored.


✅ Correct parallel async

ts
await Promise.all(items.map(save));

8. Object iteration example (real fix)

❌ Before

ts
for (const key in percentage) {
    if (!ignoredKeys.has(key)) {
        values.push(percentage[key]);
    }
}

✅ After

ts
Object.entries(percentage).forEach(([key, value]) => {
    if (!ignoredKeys.has(key) && typeof value === 'number') {
        values.push(value);
    }
});

9. Replace loops pushing promises

❌ Before

ts
for (const assignee of assignees) {
    promises.push(deleteTask(assignee));
}

✅ After

ts
const promises = assignees.map(deleteTask);
await Promise.all(promises);

Cleaner and faster.


10. Remove useless constructors

❌ Before

ts
constructor() {
    super();
}

✅ After

Delete it.


11. No unused expressions

❌ Problem

ts
status.length > 0 && status.forEach(...)

Expression result ignored.


✅ Fix

ts
if (status.length > 0) {
    status.forEach(...)
}

12. Async functions must always return

❌ Problem

ts
async function remove() {
    try {
        return await repo.delete();
    } catch (e) {
        console.error(e);
    }
}

Missing return path.


✅ Fix

ts
catch (e) {
    console.error(e);
    return null;
}

13. Avoid variable shadowing

❌ Problem

ts
const sum = Object.keys(values).reduce((acc, key) => ...

key already declared earlier.


✅ Fix

ts
.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

GoalUse
iterate arrayforEach
transform arraymap
async parallelPromise.all(map())
object iterationObject.entries
update dataclone + return
async functionalways 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 🚀