You don’t actually need if statements (EVER)
Sure they’re a nice and easy way to create control flow, but you can write many billions of lines of conditional JS code without a SINGLE if statement.
And there are many situations where a different construct shows what you wanna do way more clearly — something we can’t ignore as long we write code for humans. Not to mention lower verbosity and shorter code.
So: let’s look at some powerful if statement upgrades.
1. The AND (&&
) operator
The &&
operator, unique to JavaScript.
We it I quickly go from this:
function visitSite(user) {
if (user.isLoggedIn) {
console.log(`You are ${user.name}`);
}
console.log('Welcome to Coding Beauty.');
}
To this:
function visitSite(user) {
user.isLoggedIn && console.log(`You are ${user.name}`);
console.log('Welcome to Coding Beauty.');
}
I’ve eradicated the nested and compacted the branching logic into a one-liner.
You want to use this when there’s an if
but no matching else
; especially when the if
block has only one line.
Even if there are multiple lines you can abstract them into a separate function and apply &&
again. After all the console.log()
in our example is an abstraction itself.
So this:
function visitSite(user) {
if (user.isLoggedIn) {
console.log(`Welcome back, ${user.name}!`);
console.log(
`Your last login was on ${user.lastLoginDate}`
);
console.log(`Your account type is ${user.accountType}`);
}
console.log('Welcome to Coding Beauty.');
}
Transforms to this:
function visitSite(user) {
user.loggedIn && handleUser(user);
console.log('Welcome to Coding Beauty.');
}
function handleUser(user) {
console.log(`Welcome back, ${user.name}!`);
console.log(
`Your last login was on ${user.lastLoginDate}`
);
console.log(`Your account type is ${user.accountType}`);
}
2. Ternary operator
Ternary operators let us compact if-else
statements into a one-liner.
They’re great if-else
replacements when all conditional cases only involve assigning a value to the same variable.
Here’s an example:
let word;
if (num === 7) {
word = 'seven';
} else {
word = 'unknown';
}
Even though the DRY principle isn’t a hard and fast rule, for this instance things will be much cleaner if we used ternaries to avoid writing the variable assignment twice:
const word = num === 7 ? 'seven' : 'unknown';
Now we can even use const
to keep things immutable and pure.
Nested ternaries
And when we have 3 or more branches in the if-else
statement or we nest ifs, the cleaner ternary replacement will contain inner ternaries.
So this:
const getNumWord = (num) => {
if (num === 1) {
return 'one';
} else if (num === 2) {
return 'two';
} else if (num === 3) {
return 'three';
} else if (num === 4) {
return 'four';
} else return 'unknown';
};
Becomes:
const getNumWord = (num) =>
num === 1
? 'one'
: num === 2
? 'two'
: num === 3
? 'three'
: num === 4
? 'four'
: 'unkwown';
Some people do cry about nested ternaries though, arguing that they’re complicated and cryptic. But I think that’s more of a personal preference, or maybe poor formatting.
And it’s formatting, we have tools like Prettier that have been doing this job (and only this job) for centuries.
As long as there’s sufficient indentation you should have no problems with readability. If the branching gets much more complex than above you probably want to move some of the lower-level logic into preceding variables or tiny functions.
Speaking of readability, Prettier is changing their nested ternary style; they’ve come up with something quite unique.
The current style uses a clever combination of flat and tree-like nesting; adding further indentation for nested ternaries in the truthy branch, but keeping things flat for those in the in the falsy branch.
const animalName = pet.canSqueak()
? 'mouse'
: pet.canBark()
? pet.isScary()
? 'wolf' // Only nests this because it's in the truthy section
: 'dog'
: pet.canMeow()
? 'cat'
: pet.canSqueak() // Flat because it's in the falsy section
? 'mouse'
: 'probably a bunny';
But very soon Prettier will format the above like this:
const animalName =
pet.canSqueak() ? 'mouse'
: pet.canBark() ?
pet.isScary() ?
'wolf'
: 'dog'
: pet.canMeow() ? 'cat'
: pet.canSqueak() ? 'mouse'
: 'probably a bunny';
The main change is the ?
‘s are all now at the ending of the same line of its ending, instead of the next one.
3. Switch statement
You will find this in C-style languages like Java, C#, and Dart — and it looks exactly the same in those languages with a few semantic differences.
If ternaries are best for generating output for one variable, then switch statements are best for processing input *from* one variable:
function processUserAction(action) {
switch (action) {
case 'play': // if (action === 'play')
console.log('Playing the game...');
startGame({ mode: 'multiplayer' });
break;
case 'pause': // else if (action === 'pause')
console.log('Pausing the game...');
pauseGame();
break;
case 'stop':
console.log('Stopping the game...');
endGame();
goToMainMenu();
break;
case 'cheat':
console.log('Activating cheat mode...');
enableCheatMode();
break;
default: // else
console.log('Invalid action!');
break;
}
}
The unique power of switch statements comes from being able to omit the break
at the end of each case
and let execution “fallthrough” to the next case:
// Generate a random number between 1 and 6 to simulate rolling a dice
const diceRoll = Math.floor(Math.random() * 6) + 1;
console.log(`You rolled a ${diceRoll}!`);
switch (diceRoll) {
case 1:
console.log('Oops, you rolled a one!');
console.log('You lose a turn.');
break;
case 2:
console.log(
'Two heads are better than one... sometimes'
);
case 4:
case 6:
// else if (diceRoll === 2 || diceRoll === 4 || diceRoll === 6)
console.log('Nice roll!');
console.log('You move forward two spaces.');
break;
// ...
default:
console.log('Invalid dice roll.');
}
Most other languages with switch-case
allow this, with the notable exception of C#.
4. Key-value object
Key-value objects let us declaratively map inputs to outputs.
const weatherActivities = {
sunny: 'go to the beach',
rainy: 'watch a movie at home',
cloudy: 'take a walk in the park',
snowy: 'build a snowman',
};
const weather = 'sunny';
console.log(`Okay, so right now it's ${weather}.`);
console.log(`Why don't you ${weatherActivities[weather]}?`);
I found this invaluable when creating screens in React Native apps – each with its own loading, error and success states. Here’s a snippet of a screen in one of our apps:
// screens/course-list.tsx
// ...
import {
ActivityIndicator,
Text
// ...
} from 'react-native-paper';
type ViewState = 'loading' | 'error' | 'data';
export function Courses({ route, navigation }) {
const [viewState, setViewState] =
useState<ViewState>('loading');
const state = useAppState();
const courses = state?.courseList;
// ...
const contentDict = {
loading: <ActivityIndicator />,
error: <Text>Error</Text>,
data: <CourseList courses={courses} />,
};
const content = contentDict[viewState];
return (
<View>
<StatusBar />
{content}
</View>
);
}
// ...
Final thoughts
So if statements aren’t bad at all and are great for writing conditional logic in an easily understandable way. But it’s important to consider alternative approaches to make code shorter, clearer and even more expressive. It’s not about eliminating if statements entirely, but rather adopting effective techniques that make our code more efficient and elegant.