← thecodex.expert · The Codex Family of Knowledge
Tier 1 · Beginner · Kotlin Project

Word Counter

Count the words, characters, and lines in any text. Learn to take text apart and measure it.

🧠 Teaches how to think spoonfed, every age Last verified:

1 The Problem

We want a tool that takes some text — typed in or read from a file — and reports how many words, characters, and lines it has. It teaches the core string operations for breaking text into pieces and measuring them.

Where this shows up: word-count limits on forms and essays, reading-time estimates, search indexing, text analysis, validating input length. Measuring and slicing text is one of the most common jobs in software.

2 How to Think About It

Think about what “counting words” really means, before any code:

The plan — in plain English
1. Get the text. → 2. Characters = how long the text is. → 3. Words = split the text on whitespace and count the pieces. → 4. Lines = count how many lines were typed. → 5. Show the three numbers.

Get the text

Characters = length

Words = split on spaces, count

Lines = split on newlines, count

Show all three

3 The Build — explained part by part

Here is the complete counter. Read each part’s note below — you should understand the whole thing from the notes alone.

Kotlinwordcount.kt
/** Splits on any run of whitespace, mirroring how most languages' split() behaves. */
fun countWords(text: String): Int {
    val trimmed = text.trim()
    return if (trimmed.isEmpty()) 0 else trimmed.split(Regex("\\s+")).size
}

fun countChars(text: String): Int = text.length

fun main() {
    println("Paste your text, then press Enter twice to finish:")

    val lines = mutableListOf<String>()
    while (true) {
        val line = readLine() ?: break
        if (line == "") break // an empty line means "done"
        lines.add(line)
    }

    val text = lines.joinToString("\n")

    println("Characters: ${countChars(text)}")
    println("Words: ${countWords(text)}")
    println("Lines: ${lines.size}")
}
⚠ No in-browser playground here
Kotlin compiles to real JVM bytecode, not something a browser can run directly — running it live would need either a server-side compiler or a third-party embed, the same kind of external dependency this site avoids relying on for a core teaching example. Copy the code below and run it with a real kotlinc on your own machine instead; the “Run It” section explains exactly how.
What each part does — in plain words
fun countWords(text: String): Int — trims the text first, then splits on any run of whitespace with the regular expression Regex("\\s+"). Trimming first matters: splitting "" on whitespace gives [""] (one empty “word”), not zero, so we special-case the empty string.

while (true) { val line = readLine() ?: break; ... } — reads one line at a time with Kotlin's plain, blocking readLine(); when input runs out it returns null, and the Elvis operator ?: breaks the loop right there, with no async machinery of any kind involved.

if (line == "") break — an empty line (the user pressing Enter twice) signals “done”, exactly as in the Python version.

lines.joinToString("\n") — reassembles the collected lines into one block of text, the same shape countWords/countChars expect.
Common mistakes — and how to avoid them
✗ Calling text.split(Regex("\\s+")) directly on an empty or all-whitespace string and reporting 1 word instead of 0.
✓ Trim the text first and special-case the empty result — splitting "" on whitespace always yields [""], never an empty list.
✗ Splitting on a single literal space, text.split(" ").
✓ That turns runs of extra spaces into fake empty “words”. The regular expression \\s+ treats any run of spaces, tabs, or newlines as one separator.
✗ Forgetting that readLine() returning null and returning "" mean different things.
✓ null means the input stream itself has ended (e.g. a piped file ran out); an empty string means the user pressed Enter on a blank line. This program treats both as “stop reading”, but they are genuinely different situations worth telling apart in a program that cares.

4 Test & Prove Each Part

How do we know this works? We pull the real logic into small, plain functions and check each one against cases we already know the answer to.

'hello world' has 2 words
An empty string has 0 words, not 1
Extra spaces between words do not create fake extra words
Character counting matches the string's actual length
Kotlinwordcount_test.kt
import kotlin.test.Test
import kotlin.test.assertEquals

class WordCountTest {
    @Test
    fun twoWords() {
        assertEquals(2, countWords("hello world"))
    }

    @Test
    fun empty() {
        assertEquals(0, countWords(""))
    }

    @Test
    fun extraSpaces() {
        assertEquals(2, countWords("  hello   world  "))
    }

    @Test
    fun charCount() {
        assertEquals(3, countChars("abc"))
    }
}

Compile with kotlinc wordcount.kt wordcount_test.kt -include-runtime -d wordcount.jar and run with JUnit's own runner. countWords/countChars take a plain String, so the tests check known inputs directly, without any typed interaction at all.

5 The Interface

INPUTtexttyped lines, blank line to finish
What it expects
The quick brown fox
jumps over the lazy dog
[blank line]
OUTPUTcountscharacters, words, lines
What it returns
Characters: 43
Words: 9
Lines: 2

6 Run It & Automate It

Save the code as wordcount.kt and compile it with kotlinc wordcount.kt -include-runtime -d wordcount.jar.

Run it locally
kotlinc wordcount.kt -include-runtime -d wordcount.jar && java -jar wordcount.jar
Paste in a block of text, then press Enter on an empty line to see the counts.

A CI tool like Jenkins compiles and tests automatically whenever the code changes — every line below has a plain explanation.

What you should see when it works
Terminala real run
Paste your text, then press Enter twice to finish:
hello world
second line

Characters: 23
Words: 4
Lines: 2
If it breaks — how to fix it
🚨 The program never stops waiting for input
Press Enter on a truly empty line (no trailing spaces) to signal “done” — a line with just a space is not empty and will not end the loop.
🚨 The word count is higher than expected
Check for extra blank lines or trailing whitespace within the pasted text; each run of whitespace counts as exactly one separator, but a stray punctuation-only line still adds its own word count.
GroovyJenkinsfile
// Jenkinsfile &mdash; compiles and tests automatically every time the code changes.
pipeline {
    agent any                                  // run on any available machine

    stages {
        stage('Get the code') {
            steps { checkout scm }             // download the latest code
        }
        stage('Set up Kotlin') {
            steps {
                sh 'kotlinc -version'                             // confirm the compiler is installed
            }
        }
        stage('Compile and test') {
            steps {
                sh 'kotlinc wordcount.kt wordcount_test.kt -include-runtime -d build.jar'  // one real JVM jar, no build tool required
                sh 'java -cp build.jar:kotlin-test-junit.jar:junit.jar org.junit.runner.JUnitCore WordCountTest'
            }
        }
    }

    post {
        success { echo 'All tests passed.' }
        failure { echo 'A test failed &mdash; look above.' }
    }
}
🎯 Try this next — make it yours

You have a working word counter. Extend it:

  1. Read from a file argument instead. Accept a filename on the command line rather than typed text. (Teaches: args: Array<String> and File(args[0]).readText().)
  2. Count the most frequent word. Build a frequency map and report the top entry. (Teaches: groupingBy/eachCount, used again in the log analyser project.)
  3. Ignore case and punctuation. Normalise before counting so “Hello” and “hello,” count as the same word. (Teaches: lowercase() and a cleanup regex.)
  4. Report reading time. Estimate minutes at an average reading speed. (Teaches: simple derived statistics.)
What you learned
You learned Kotlin’s plain, synchronous readLine() for line-at-a-time input with no async code anywhere, the Elvis operator ?: for a clean “stop here” when input ends, and Regex for whitespace-aware splitting. Related reference: Collections in Kotlin, Standard Library Deep Dive.