Part of Github Copilot with Kotlin

Kotin: Create an extension function for Integer to check if it is prime or not

Sandy LaneSandy Lane

Video: Kotin: Create an extension function for Integer to check if it is prime or not by Taught by Celeste AI - AI Coding Coach

Take the quiz on the full lesson page
Test what you've read · interactive walkthrough

Kotlin: Extension Function for Int.isPrime()

fun Int.isPrime(): Boolean { ... } adds a method to a type you don't own. Test only odd divisors up to √n. The trial-division algorithm in idiomatic Kotlin.

A small puzzle that teaches Kotlin's extension functions while implementing a classic primality test. The result reads as if isPrime had always been part of Int.

The Copilot prompt

// Extension function on Int that returns true if the number is prime
fun Int.isPrime(): Boolean {

Copilot generates:

fun Int.isPrime(): Boolean {
  if (this <= 1) return false
  if (this == 2) return true
  if (this % 2 == 0) return false

  val sqrt = kotlin.math.sqrt(this.toDouble()).toInt()
  for (i in 3..sqrt step 2) {
    if (this % i == 0) return false
  }
  return true
}

fun main() {
  println((7).isPrime())   // true
  println((10).isPrime())  // false
  println((97).isPrime())  // true
}

Walkthrough

1. Three early-exit guards. - this <= 1 — primes are by definition > 1. - this == 2 — the only even prime. - this % 2 == 0 — every other even is composite.

After these, we know this is odd and ≥ 3.

2. Trial division with step 2. The loop tries divisors 3, 5, 7, 9, 11, ... up to √n. Skipping evens halves the work.

3. √n upper bound. If n has a divisor > √n, it must also have one < √n (because divisors come in pairs). So we only need to check up to √n.

kotlin.math.sqrt(this.toDouble()).toInt() — convert to double, take the square root, truncate to int.

4. Default to true. If no divisor was found, n is prime.

Extension function syntax

fun Int.isPrime(): Boolean { /* this is the Int */ }

Int. before the function name says "this function looks like a method on Int." Inside the body, this refers to the Int being checked.

Calling: 7.isPrime() looks like a method call on a built-in type. Behind the scenes, it's a static call: IsPrimeKt.isPrime(7).

You can extend any type — String, List<T>, MutableMap<K, V>, your own classes, even nullable types:

fun String?.isNullOrEmpty(): Boolean = this == null || this.isEmpty()

(That one already exists in the stdlib — String?.isNullOrEmpty() is built in.)

Performance

Trial division is O(√n). For n = 10^9, that's ~30,000 iterations — fast enough for one number. For many primes, switch to the Sieve of Eratosthenes:

fun primesUpTo(n: Int): List<Int> {
  if (n < 2) return emptyList()
  val isComposite = BooleanArray(n + 1)
  val primes = mutableListOf<Int>()
  for (i in 2..n) {
    if (!isComposite[i]) {
      primes.add(i)
      var j = i.toLong() * i
      while (j <= n) {
        isComposite[j.toInt()] = true
        j += i
      }
    }
  }
  return primes
}

The sieve generates all primes up to n in O(n log log n) time — much faster for "give me the first 1000 primes" than testing each one individually.

For huge primes (cryptography), you need probabilistic tests like Miller-Rabin. Trial division dies past ~10^15.

Edge cases

  • 0 and 1. Not prime (handled by this <= 1).
  • Negative numbers. Not prime (handled by same guard).
  • 2. The only even prime (special case).
  • Very large Int. Max value is ~2.1×10⁹. sqrt(MaxInt).toInt() is ~46,341. Loop runs ~23,000 iterations max. Fast.
  • Long instead of Int. Add a parallel Long.isPrime() extension. Trial division still works up to ~10^18 in reasonable time.

Filtering primes

val primes = (1..100).filter { it.isPrime() }
println(primes)   // [2, 3, 5, 7, 11, 13, 17, ..., 97]

The extension reads naturally inside filter. Copilot will often suggest exactly this pattern when you ask "list of primes from 1 to 100."

Counting primes

val countPrimesUnder100 = (1..100).count { it.isPrime() }   // 25

Same shape as episode 14 — the count(predicate) idiom.

Why use an extension?

Compare two designs:

// Free function
fun isPrime(n: Int): Boolean { /* ... */ }
val primes = (1..100).filter { isPrime(it) }

// Extension
fun Int.isPrime(): Boolean { /* ... */ }
val primes = (1..100).filter { it.isPrime() }

The extension reads as if isPrime were always part of Int. It's a stylistic choice — for one-off helpers, free functions are fine. For functions that semantically belong to a type, extensions feel more natural.

Common mistakes

Iterating up to n / 2 instead of √n. Works but slower. √n is the optimal upper bound.

Forgetting step 2. Tests every divisor (including evens), doubling the work. With step 2 plus the early % 2 check, only odd divisors are tried.

Not handling 0/1/negatives. Mathematical edge cases. Always this <= 1 → false.

Using pow(0.5) instead of sqrt. Works but slower. Stick with kotlin.math.sqrt.

Recomputing √n inside the loop. for (i in 3..sqrt(this.toDouble()).toInt() step 2) recomputes every iteration. Compute once outside.

Treating Long like Int. (10L).isPrime() won't compile if you only defined Int.isPrime(). Define both, or generic with Number.

What's next

Episode 19: List of weekday and weekend abbreviations. A small list-creation puzzle that demos how Copilot reads context from comments to produce two parallel lists.

Recap

fun Int.isPrime(): Boolean { ... } adds a method to Int. Trial division: skip 1, 2 special-case, skip evens, trial from 3 to √n step 2. O(√n) per check; for many primes use the Sieve of Eratosthenes (O(n log log n)). Extension functions read more naturally than free functions when the operation logically belongs to a type. Watch the upper bound — √n is enough.

Next episode: weekday/weekend lists.

Ready? Take the quiz on the full lesson page →
Test what you've learned. Watch the lesson and try the interactive quiz on the same page.