Is it a leap year?
Creating a function to determine whether a year is a leap year.
Next year is a leap year. How can we tell whether a year is a leap year? The rule is simple: it must be divisible by four. Like all rules, this has exceptions: a year divisible by 100 is not a leap year unless it is also divisible by 400. For more details, see Wikipedia.
Let's look at some examples:
- 2000: Divisible by 4, 100 and 400, so it is a leap year.
- 2019: Not divisible by 4, so it is not a leap year.
- 2020: Divisible by 4 but not by 100, so it is a leap year.
- 2100: Divisible by 4 and 100 but not by 400, so it is not a leap year.
This is a simple programming exercise, but I found it very interesting.
Initial solution
I generally solve problems by finding a working solution first, then refactoring it into something more elegant or efficient.
This was my initial solution:
export const isLeap = (year) => {
if (year % 4 === 0) {
if (year % 100 === 0) {
if (year % 400 === 0) {
return true;
}
return false;
}
return true;
}
return false;
};
I simply translated the rules above literally into JavaScript. It solves the problem, but can be improved.
Refactoring
Refactoring with guard clauses
My first idea was to remove the nested if statements using guard clauses. Here is the code:
export const isLeap = (year) => {
if (year % 400 === 0) {
return true;
}
if (year % 100 === 0) {
return false;
}
return year % 4 === 0;
};
The code checks the most specific conditions first, then the more general ones. This avoids redundant comparisons.
- If a year is divisible by 400, it is also divisible by 100 and 4, so it is a leap year.
- If the year is divisible by 100, we can confidently say it is not a leap year, because at this point we already know it is not divisible by 400.
- Finally, if we reach the last
return, we know the year is divisible by neither 400 nor 100. Only the general rule applies: it is a leap year if divisible by 4.
To learn more about guard clauses, I recommend this definition: Refactoring: Guard Clauses, and this video from Carlos Buenosvinos's RigorTalks series, which explains them very well.
In my opinion, this solution is much better than the original, but it is still somewhat verbose. Can we reduce it further?
Refactoring: Removing the if statements
Francesco Cirillo, creator of the Pomodoro Technique, also created a curious campaign: the Anti-if Campaign. Can we solve this problem without if? Let's see.
First step
Remove the innermost if and return the result of year % 400 === 0 directly.
export const isLeap = (year) => {
if (year % 4 === 0) {
if (year % 100 === 0) {
return year % 400 === 0
}
return true;
}
return false;
};
Second step
Following the same idea, remove the nested if. This time we must combine it with the previous return value and slightly change the condition.
If the year is not divisible by 100 (year % 100 !== 0) or (||) is divisible by 400 (year % 400 === 0), it is a leap year. In JavaScript, this becomes year % 100 !== 0 || year % 400 === 0.
export const isLeap = (year) => {
if (year % 4 === 0) {
return year % 100 !== 0 || year % 400 === 0;
}
return false;
};
Final step
Now remove the last if. To guarantee a leap year, both the if condition (year % 4 === 0) and (&&) the return condition (year % 100 !== 0 || year % 400 === 0) must hold. This gives the following JavaScript code:
export const isLeap = (year) => {
return year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0);
};
We have now removed every if and reduced our code to one line.
Learning from others
Even when we are happy with our solutions, looking at other people's work is a great idea because we will surely learn something new. I found lainjiang's solution particularly interesting. It is also one line, but uses different conditions.