This is a big mistake many developers make that makes code cryptic and unreadable.
Don’t do this:
// ❌ Bad: negative name
const isNotVisible = doStuff();
const isDisabled = doStuff();
const isNotActive = doStuff();
const hasNoAccess = doStuff();
// ❌ Double negation
console.log(!isNotVisible);
console.log(!isDisabled);
console.log(!isNotActive);
console.log(!hasNoAccess);
Double negatives like this makes it much harder to think about the logic and conditionals in your code.
It’s much better to name them positively:
// ✅ Good: positive name
const isVisible = doStuff();
const isEnabled = doStuff();
const isActive = doStuff();
const hasAccess = doStuff();
// ✅ Clear and readable
console.log(isVisible);
console.log(isEnabled);
console.log(isActive);
console.log(hasAccess);
There is no indirection and your brain takes zero time to parse this.
Just imagine the pain of trying to understand this:
I didn’t not forget about not being unable to use the account.
Lol I couldn’t even understand it myself even though I made it up.
Compare to the far more natural way you’d expect from a sensible human:
I remembered that I could use the account.
Control flow negation
This is a more delicate form of double negation you need to know about:
const isAllowed = checkSomething();
// ❌ Bad
if (!isAllowed) {
handleError();
} else {
// ❌ double negation!
handleSuccess();
}
It’s double negation because we’re checking for the negative first.
So the else
clause becomes a not of this negative.
Better:
const isAllowed = checkSomething();
// ✅ Fix: invert the if
if (isAllowed) {
handleSuccess()
} else {
handleError();
}
The same thing for equality checks:
// ❌ Double negation
if (value !== 0) {
doError();
} else {
doSuccess();
}
// ✅ Better
if (value === 0) {
doSuccess();
} else {
doError();
}
Even when there’s no positive condition you can leave it blank — to keep the negative part in the else
:
const hasAlreadyFetched = false;
if (hasAlreadyFetched) {
// nothing to do
} else {
doSomething();
}
This is great for expressions that are awkward to negate, like instanceof
:
// ❌ Bad
if (!(obj instanceof Person)) {
doSomething();
}
// ✅
if (obj instanceof Person) {
} else {
doSomething();
}
Exception: guard clauses
In guard clauses we deliberately deal with all the negatives first before the positive.
So we return
early and avoid deeply nested ifs:
❌ Instead of this:
function sendMoney(account, amount) {
if (account.balance > amount) {
if (amount > 0) {
if (account.sender === 'user-token') {
account.balance -= amount;
console.log('Transfer completed');
} else {
console.log('Forbidden user');
}
} else {
console.log('Invalid transfer amount');
}
} else {
console.log('Insufficient funds');
}
}
✅ We do this:
// ✅ Much cleaner
function sendMoney(account, amount) {
if (account.balance < amount) {
console.log('Insufficient funds');
return;
}
if (amount <= 0) {
console.log('Invalid transfer amount');
return;
}
if (account.sender !== 'user-token') {
console.log('Forbidden user');
return;
}
account.balance -= amount;
console.log('Transfer completed');
}
See, there’s no hard and fast rule. The end goal is readability: to make code as easy to understand in as little time as possible.
Flags always start out as false
Flags are boolean variables that control program flow.
A classic use case is running an action as long as the flag has a particular value:
let val = false;
while (true);
// do something that changes val
if (val) {
break;
}
}
In cases like this, always initialize the flag to false
and wait for true
.
Flags should always start out as false.
// ❌ waiting for true -> false
let isRunning = true;
while (true) {
// processing...
if (!isRunning) {
break;
}
}
// ✅ waiting for false -> true
let hasStopped = false;
while (true) {
// processing...
if (hasStopped) {
break;
}
}
This makes so much sense when you understand flags to be a signal — that something is there.
Compound conditions
Negation also makes complex boolean expressions much harder to understand.
❌ Before:
if (!sleepy && !hungry) {
console.log('time for gym👟');
} else {
console.log('what to do now...');
// ❌ hard to understand when this runs
}
This is where De Morgan’s Laws come in:
A powerful rule set for smoothly simplifying complex booleans and removing excessive negation:
let a: boolean;
let b: boolean;
!(a && b) === !a || !b;
!(a || b) === !a && !b;
✅ So now:
if (!(sleepy || hungry)) {
console.log('time for gym👟');
} else {
console.log('what to do now...');
}
Now we can easily invert the logic as we did before:
if (sleepy || hungry) {
console.log('what to do now...');
} else {
console.log('time for gym👟');
}
It’s also structurally similar to the English in this way:
The first version was like:
(!a && !b)
It’s time for gym cause I’m not sleepy and I’m not hungry
After the refactor:
!(a || b):
It’s time for gym cause I’m not sleepy or hungry.
That’s how we’d typically say it.
Key points
- Boolean variable names should be in the positive form. Exception: flags
- Always check the positive case first in
if-else
statements. Exception: Guard clauses - Use De Morgan’s Laws to simplify negation in compound conditions.
11 Amazing New JavaScript Features in ES13
This guide will bring you up to speed with all the latest features added in ECMAScript 13. These powerful new features will modernize your JavaScript with shorter and more expressive code.