← thecodex.expert · The Codex Family of Knowledge
Tier 1 · Beginner · Go 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 word counter. Each part is explained below.

Gowordcount.go
package main

import (
	"bufio"
	"fmt"
	"os"
	"strings"
)

func countWords(text string) int { return len(strings.Fields(text)) }
func countChars(text string) int { return len(text) }

func main() {
	fmt.Println("Paste your text, then press Enter twice to finish:")

	var lines []string
	scanner := bufio.NewScanner(os.Stdin)
	for scanner.Scan() {
		line := scanner.Text()
		if line == "" { // an empty line means "done"
			break
		}
		lines = append(lines, line)
	}

	text := strings.Join(lines, "\n")

	charCount := countChars(text)
	wordCount := countWords(text) // Fields splits on any whitespace
	lineCount := len(lines)

	fmt.Printf("Characters: %d\n", charCount)
	fmt.Printf("Words: %d\n", wordCount)
	fmt.Printf("Lines: %d\n", lineCount)
}
⚠ No in-browser playground here
Go compiles to a real binary, so unlike the Python version of this project there is no editor above you can run in the browser. Copy the code below and run it on your own machine — it takes seconds once Go is installed.
What each part does — in plain words
bufio.NewScanner(os.Stdin) — Go’s idiomatic way to read line by line. scanner.Scan() returns true and loads the next line into scanner.Text(), or returns false when there is nothing left (or, here, when we break on a blank line).

strings.Join(lines, "\n") — glue the collected lines back into one block of text, with a newline between each — the same job as Python’s "\n".join(lines).

len(text) — this is the one place Go and Python quietly differ: Go’s len() on a string counts bytes, not visible characters. For plain English text every character is one byte, so it works here — but text with emoji or accented letters would need utf8.RuneCountInString instead (see the mistake below).

strings.Fields(text) — this is the key move, and it matches Python’s split() exactly: it splits on any run of whitespace (spaces, tabs, newlines) and ignores repeats, handing back a slice of words. len() of that slice is the word count.

len(lines) — we already collected the lines into a slice, so counting them is just its length.
Common mistakes — and how to avoid them
✗ Splitting on strings.Split(text, " ") with a literal space — then double spaces create empty “words”.
✓ Use strings.Fields(text); it handles any whitespace and ignores repeats.
✗ Assuming len(text) always means “number of characters” — it counts bytes, so text with é, €, or emoji will count higher than the number of characters a person sees.
✓ For true character counts on non-English text, use utf8.RuneCountInString(text) from the unicode/utf8 package instead.
✗ Forgetting the blank-line check — the scanner keeps reading until the input stream itself closes, which never happens in an interactive terminal.
✓ Check if line == "" { break } inside the loop, exactly as above.

4 Test & Prove Each Part

We test the counting logic on text where we already know the answer — pulling countWords and countChars into their own functions makes this possible without typing anything by hand.

"hello world" has 2 words
An empty string has 0 words
Extra spaces do not create fake words
Counting characters matches the text length
Gowordcount_test.go
package main

import "testing"

func TestTwoWords(t *testing.T) {
	if got := countWords("hello world"); got != 2 {
		t.Errorf(`countWords("hello world") = %d; want 2`, got)
	}
}

func TestEmpty(t *testing.T) {
	if got := countWords(""); got != 0 {
		t.Errorf(`countWords("") = %d; want 0`, got)
	}
}

func TestExtraSpaces(t *testing.T) {
	if got := countWords("  hello   world  "); got != 2 {
		t.Errorf(`countWords("  hello   world  ") = %d; want 2`, got)
	}
}

func TestCharCount(t *testing.T) {
	if got := countChars("abc"); got != 3 {
		t.Errorf(`countChars("abc") = %d; want 3`, got)
	}
}

Run with go test -v ./.... The extra-spaces test is important: strings.Fields cleverly ignores repeated spaces, so “hello   world” is still 2 words, not more — the same guarantee Python’s split() makes.

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.go and run it with go run wordcount.go — Go compiles and executes in one step, no separate build needed while you are experimenting.

Run it locally
go run wordcount.go
Type or paste text, then press Enter on a blank line to see the counts.

A CI tool like Jenkins runs go test 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:
The quick brown fox
jumps over the lazy dog

Characters: 43
Words: 9
Lines: 2
If it breaks — how to fix it
🚨 Word count is higher than expected.
You probably split on a literal " ". Switch to strings.Fields(text).
🚨 The program never stops asking for input.
Your blank-line check is missing or wrong. It must break as soon as scanner.Text() returns an empty string.
GroovyJenkinsfile
// Jenkinsfile — runs the 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 Go') {
            steps {
                sh 'go version'                                 // confirm Go is installed
                sh 'test -f go.mod || go mod init word_counter'  // create a module if none exists
            }
        }
        stage('Run the tests') {
            steps {
                sh 'go vet ./...'                    // catch obvious mistakes before running
                sh 'go test -v ./...'                // run every test, show each result
            }
        }
    }

    post {
        success { echo 'All tests passed.' }
        failure { echo 'A test failed — look above.' }
    }
}
🎯 Try this next — make it yours
  1. Read from a file. Use os.ReadFile to count a whole document instead of typed text. (Teaches: file reading.)
  2. Reading time. Estimate minutes to read (words ÷ 200). (Teaches: a calculation.)
  3. Most common word. Find which word appears most, using a map[string]int. (Teaches: counting with a map.)
What you learned
You learned the core string operations: len() to measure bytes, strings.Fields() to break text into words, and strings.Join() to glue it back — plus the byte-vs-rune distinction that trips up a lot of newcomers to Go. Related: Strings & Runes, Standard Library.