Setup & stdin-echo
stdin stdout
│ ▲
▼ │
───────┐ line-by-line ┌─────────┐
│ scanner.Scan() │ │
bufio │ ───────────────────► │ fmt. │
.Scanner│ scanner.Text() │ Fprintln│
│ │ │
───────┘ └─────────┘
(your Echo function)
What you'll build
A program that reads stdin line-by-line and writes each line unchanged to stdout. Five lines of Go. Sixty seconds of typing. And yet this is the kata I most want you to drill until it's muscle memory — because every Maelstrom node in Phase 5 is this program with more logic.
The idiom: bufio.Scanner
Go's stdlib gives you three ways to read text:
io.Reader.Read(p []byte) (n int, err error) // low-level bytes, you own the buffer
bufio.NewReader(r).ReadString('\n') // manual line parsing
bufio.NewScanner(r) // high-level, line-at-a-time
Scanner is the right tool for line-delimited text. It handles end-of-line, manages the buffer, and gives you a clean three-line loop:
scanner := bufio.NewScanner(r)
for scanner.Scan() {
line := scanner.Text()
// do something with line
}
if err := scanner.Err(); err != nil {
return fmt.Errorf("scanning: %w", err)
}
Three things to notice:
1. Scan() returns bool, not (line, err). It returns true until EOF or error. You check Err() afterward if you care whether the loop exited cleanly. 2. Text() is the current line, without the trailing newline. You decide whether to put it back when you write. 3. EOF is not an error in Go. Scan() returning false at EOF is success. Treat io.EOF as a signal, not a failure — that's the idiom.
The JS/Python hook
In Node you'd do readline.createInterface({ input: process.stdin }). In Python you'd for line in sys.stdin:. Go is the same shape with explicit types: r io.Reader, w io.Writer. The function signature Echo(r io.Reader, w io.Writer) error is deliberately testable — you can pass it strings.NewReader("hello\n") and a bytes.Buffer instead of os.Stdin and os.Stdout. Writing testable I/O in Go means accepting interfaces, not globals. This is the pattern you'll reach for a thousand times.
What to avoid
fmt.Scanln— treats whitespace as a delimiter. Wrong for line-based input.panicing on EOF — EOF is a normal end-of-stream, not an error.- Reading into a fixed
[]byteand splitting on\nyourself — you'll get the buffer-size bug right when you least want to. - Using a global buffer — makes the function untestable.
The Maelstrom payoff
Every Gossip Glomer challenge starts with a Maelstrom node that reads JSON messages from stdin, one per line, and writes JSON messages to stdout. Your Echo function — scanner in, Fprintln out — is literally the outer loop. When you recognize this on day 100 of the curriculum, you'll thank yourself for drilling it on day one.
Verify
Edit exercises/phase-1/1.1-hello-world/echo.go in your go-dojo repo. The test is already written.
go-dojo verify 1.1-hello-world
Target for automatic mastery: under 5 minutes from blank editor, no references.
Mastery criteria
- go env, go mod init, go run, go test, go vet, go fmt used fluently
- Can write a stdin→stdout line-echo program from scratch in <5 min without reference
- Explain when bufio.Scanner is wrong (long lines) and how to fix it
Verify
Run from your go-dojo repo root:
go-dojo verify 1.1-hello-world
This runs the task's go test suite in exercises/phase-1/1.1-hello-world/, commits the attempt to .go-dojo/attempts/, and updates your mastery.
Further reading
- Go documentation — bufio.Scanner The API reference. Read the examples. This is the habit to build — stdlib docs first.
- The Go Programming Language Specification — Declarations and scope Feel the shape of Go's type system. Skim, don't memorize.
- Learn Go with Tests — Hello, World Chris James's TDD-first intro. Works through the same red/green loop go-dojo uses.
- Rob Pike — Go at Google (transcript) Why Go exists. Two pages. If you have 15 minutes, read this before writing any code.
- Effective Go — Formatting and names `gofmt` runs on save. No bikeshedding allowed. This is the idiom sheet.