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.
Every Crazy Thing JavaScript Does
A captivating guide to the subtle caveats and lesser-known parts of JavaScript.