Back to Beginner: I/O, Arithmetic, Loops
Q23Question 23 of 30

Leap Year: check whether a year is a leap year

Key Concepts
  • A year is leap when divisible by 4 and not by 100, or divisible by 400: this is the full Gregorian rule.
  • 1900 is NOT a leap year (divisible by 100 but not 400); 2000 IS a leap year (divisible by 400).
  • In production, use java.time.Year.of(year).isLeap() which implements the exact Gregorian rule.
  • Leap year detection feeds day-count conventions for interest: 366 days in a leap year, 365 otherwise.
  • The century-year exception is the classic trap; operator precedence in the boolean condition must be correct.
  • Different banking products use different day-count conventions; the leap rule only matters for actual-days conventions.

Definition

A year is leap when divisible by 4 and not by 100, or divisible by 400, and this directly decides the day count used for interest.
  • Gregorian rule – divisible by 4, except centuries, which must also be divisible by 400.
  • Boolean condition – (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0).
  • Standard library – Year.of(year).isLeap() is the production choice, battle-tested and correct.
  • Banking impact – leap years have 366 days, affecting the denominator in actual-days interest conventions.
  • Classic trap – 1900 is not leap (100 but not 400); 2000 is leap (400).

National Bank of India computes interest using the actual number of days in the year: 366 for a leap year, 365 otherwise. For the year 2000, which is divisible by 400, the answer is leap, so interest uses 366 days.

Understanding the Concepts

What is the Gregorian leap year rule?

  • A year is a leap year when:
  • It is divisible by 4, AND
  • It is NOT divisible by 100, UNLESS
  • It is also divisible by 400.

As one boolean: (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0).

How do you implement it?

JAVA
package com.bank.service;

import java.time.Year;

public class LeapYearService {

    public static boolean isLeapYear(int year) {
        return (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0);
    }

    public static boolean isLeapYearStdLib(int year) {
        return Year.of(year).isLeap();
    }

    public static void main(String[] args) {
        int[] years = {1900, 2000, 2024, 2023, 1600};
        for (int y : years) {
            System.out.println(y + " -> " + (isLeapYear(y) ? "Leap" : "Not Leap")
                + " (stdlib: " + isLeapYearStdLib(y) + ")");
        }
    }
}
TEXT
1900 -> Not Leap (stdlib: false)
2000 -> Leap (stdlib: true)
2024 -> Leap (stdlib: true)
2023 -> Not Leap (stdlib: false)
1600 -> Leap (stdlib: true)

Why is the century rule a trap?

Most people remember "divisible by 4" but forget the century exception. 1900 is divisible by 4, but it is also divisible by 100 and NOT by 400, so it is not a leap year. 2000 is divisible by 400, so it is a leap year. Getting this wrong is the classic bug.

Why use Year.isLeap() in production?

Date rules are notoriously easy to get subtly wrong. The standard library implementation is battle-tested and handles edge cases. Writing the explicit condition is fine for learning, but in production code that touches money, delegate to the standard library.

Why does this matter for banking?

Under an actual/actual day-count convention, the denominator for annualizing interest depends on whether the year is a leap year. A leap year uses 366 days, a normal year 365. Getting this wrong misprices interest.

JAVA
// Interest calculation
int daysInYear = isLeapYear(year) ? 366 : 365;
BigDecimal dailyRate = annualRate.divide(
    new BigDecimal(daysInYear), 10, RoundingMode.HALF_EVEN
);

What are edge cases?

  • Year 0: technically leap by the mathematical rule (divisible by 400).
  • Negative years (BCE): the modulo rule still works.
  • Year 100: not leap (divisible by 100, not by 400).

What are common mistakes?

JAVA
// Wrong: only checking divisible by 4
return year % 4 == 0; // says 1900 is leap (WRONG)

// Wrong: wrong operator precedence
return year % 4 == 0 && year % 100 != 0 || year % 400 == 0;
// Without parentheses, && binds tighter, but it still works by chance
// Always use explicit parentheses for clarity!

// Correct: explicit parentheses
return (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0);

What should a senior engineer know?

Different products use different day-count conventions. The 30/360 convention ignores leap years entirely. The actual/365 convention always uses 365 regardless. Only the actual/actual convention needs leap-year detection. A senior engineer keeps the day-count logic explicit and well-tested, and knows which convention each product uses.

What are common interview follow-up questions?

  • How many leap years are there between year A and year B?
  • What day-count conventions exist in banking?
  • How does java.time.Year.isLeap() handle negative years?

Visual Overview

DIAGRAM
       ┌───────────────┐
       │   Read year   │
       └──────┬────────┘
              ▼
       ┌──────────────┐
       │ year%400==0? │──Yes──→ Leap Year
       └──────┬───────┘
              │ No
              ▼
       ┌──────────────┐
       │ year%100==0? │──Yes──→ Not a Leap Year
       └──────┬───────┘
              │ No
              ▼
       ┌──────────────┐
       │ year%4==0?   │──Yes──→ Leap Year
       └──────┬───────┘
              │ No
              ▼
        Not a Leap Year

Key Takeaway

A year is a leap year when it is divisible by 4, with the exception that century years are not leap unless also divisible by 400. The single boolean condition is: divisible by 4 and not by 100, or divisible by 400. The classic trap is the century rule: 1900 is not a leap year, but 2000 is.

In banking, leap-year detection feeds the day-count convention for interest. Under actual/actual, a leap year has 366 days and a normal year 365, which changes the interest calculation. Getting it wrong misprices interest. For that reason, production code should use Year.of(year).isLeap() from the standard library, which implements the exact Gregorian rule and is battle-tested.

The explicit modulo condition is fine for learning and interviews, but date rules are deceptively tricky. Operator precedence, the century exception, and the exception-to-the-exception are all places where hand-rolled logic can go wrong.

In a banking project at National Bank of India, the interest engine's day-count logic used the LeapYearService to determine whether each year in the accrual period was a leap year. For 2000, the service correctly reported leap and interest used 366 days.

Remember one sentence: a year is leap when divisible by 4 except centuries unless also divisible by 400, and in production use Year.isLeap().

---

Share
Use ← → arrow keys to navigate