- Lab
-
Libraries: If you want this lab, consider one of these libraries.
- Core Tech
Repetition and Delegation in Go
The Thursday Night Trivia League needs a small command-line scorekeeper. Hosts type short commands to record team scores each round, and the tool must never crash on a bad command, must let a host enter an unknown number of scores per round, must summarize team totals and find the leading team, and must delegate the repeated work of validating and averaging scores to reusable functions that report both a result and whether it succeeded.
Lab Info
Table of Contents
-
Challenge
Step 1: Meet the Thursday Night Trivia League scorekeeper
The Thursday Night Trivia League needs a scorekeeper
Every Thursday, hosts at the Thursday Night Trivia League read out short commands and numbers as teams call in their round scores. Right now that all happens on paper, and paper doesn't stop a host from typing
addtwice or forgetting how many scores a round actually had. You're building the command-line tool that will replace the paper: it reads a host's command, collects however many scores a round produces, tallies totals per team, and reports who's leading.This lab builds that tool in the order a host would actually use it: first make the tool understand commands, then let it collect scores, then have it summarize teams, and finally wire the repeated logic into small functions the rest of the program can call with confidence. ## Touring the starter files
The workspace already has five files with stubbed functions, each owning one piece of the scorekeeper, plus
main.go, which replays a whole Thursday night using those pieces:command_router.go: turns a host's typed word into an action, and cleans up messy input before it gets there.score_entry.go: collects scores from a round, whether the round has an unknown number of entries or a fixed makeup count.team_summary.go: adds up a team's scores and lists every team name currently in play.score_utils.go: reusable helpers for averaging scores safely and validating a batch of them at once.standings.go: finds the leading team, announces it, and applies bonus points to a stored total.main.go: the program you run at the end. It already calls your functions, and it contains one block of inline code you will refactor in Step 5.
Everything compiles as-is, and the functions you'll complete are stubbed with a
// TODOcomment and a// Hintline. Every step below points you at the next TODO and explains the Go control-flow or function idiom it exercises. ## How the steps build on each other- Step 2 teaches
if/elseandswitchwith a fallback branch, so the router never chokes on an unexpected command. - Step 3 teaches the three
forloop shapes: the infinite loop withbreak, the counter-based loop, and the loop with only a condition. - Step 4 teaches
rangeover both a slice and a map, so team totals and team names fall out of a single pass. - Step 5 turns repeated logic into small helpers with value, pointer, and variadic parameters, and with single and multi-value returns such as the boolean ok-idiom. You'll also refactor inline code from
main.gointo a function.
Run
go test . -count=1at any point to see which TODOs still need work. Move on to Step 2 when you're ready to make the router talk back. -
Challenge
Step 2: Route host commands with conditionals
Making sense of a typed command
A host doesn't type structured data. They type
add,end, orquit, sometimes with stray capitalization or spaces. Go'sswitchstatement, with adefaultcase, is the cleanest way to turn a small set of known strings into actions while still handling everything else safely.Open
command_router.goand look atRouteCommand. For now it returns an empty string. You'll write aswitchwhose cases map recognized commands to actions, plus adefaultfallback for anything else. ## Falling back gracefully on ranges of valuesA
switchis great for exact-match strings, but classifying a number into buckets like"low"or"high"reads more naturally as anif/elsechain, where each branch narrows the range and the finalelsecatches whatever is left. The order of the checks matters: the first branch that matches wins, so put the most restrictive condition first. ## One last defense before the switchRouteCommand'sdefaultcase only helps if the string it receives is already close to what it expects. A host who types" ADD "shouldn't get "unknown command" just because of stray spaces or casing. Cleaning the string first, and giving empty input its own safe fallback value, keeps that defense meaningful. -
Challenge
Step 3: Collect round scores with loops
Collecting a round of unknown length
A trivia round might have three scores or eight. The host keeps entering them until they're done, and the tool has no way to know the count ahead of time. Go's
forloop without any condition (for { ... }) runs forever until something inside the block explicitlybreaks out of it, which is exactly the shape you need when a reader function tells you, call by call, whether there's more to read. ## Collecting a fixed number of makeup scoresMakeup rounds are different: the host already knows exactly how many scores they're entering. That's the classic case for a counter-based
forloop,for i := 0; i < count; i++, where the loop runs a known, fixed number of times instead of waiting for a signal to stop. ## Skipping values inside a counter loopCounter-based loops aren't just for collecting. They're also useful for filtering, using
continueto skip an index without processing it further. This keeps the loop's index available while still letting you leave certain elements out of the result entirely. ## Looping while a condition holdsThere is a third
forshape. Writingfor condition { ... }repeats the block for as long as the condition is true, which is what other languages call awhileloop. Use it when you don't know how many passes you need but a condition decides when to stop, such as adding rounds until a team's total reaches a target.Because only the condition ends the loop, something inside the block must move the condition toward false. If it never does, the loop runs forever, so check for inputs that could cause that before the loop starts.
-
Challenge
Step 4: Summarize teams by ranging over collections
Summing a slice with range
Once a round's scores are collected into a slice, adding them up is a job for
range. Ranging over a slice gives you the index and the value on each pass; when you only need the value, you can discard the index with_. This is the idiomatic replacement for a counter loop whenever you don't actually need the index. ## Ranging over a map, and why order mattersTeam totals live in a
map[string]int, and ranging over a map gives you its keys (and values, if you ask for them), but in an order Go deliberately randomizes between runs. That's fine for summing, but not for reporting: a standings list needs to come out the same way every time it's asked for, so any function that returns team names from a map should sort them before returning. -
Challenge
Step 5: Delegate to functions and report standings
Returning a result and whether it's trustworthy
So far, functions have returned one value each. But some computations don't always have a sensible answer. You can't average an empty round. Go's convention for this is the ok-idiom: return the computed value alongside a boolean that tells the caller whether that value actually means something, exactly like a map lookup's
v, ok := m[key]. ## Accepting a call site's worth of scores at onceA host validating scores doesn't want to build a slice first just to call a function. They want to pass however many scores they have directly. A variadic parameter, written
...int, lets a function accept zero, one, or many arguments of the same type at the call site, while the function body still sees them as an ordinary slice. ## Reporting two things about a map at onceFinding the leading team means answering two questions together: who, and by how much. A function can return both by listing two result types,
(string, int), and returning both values in the samereturnstatement. No wrapper struct is required for a result this simple. ## Changing a caller's value through a pointerEvery function so far has taken value parameters, which are copies. Changes inside the function don't reach the caller's original variable. A pointer parameter,
*int, is different: it holds the address of the caller's variable, so dereferencing it with*and assigning through it changes the value the caller sees too. That's the right tool when a function's whole purpose is to mutate something the caller already owns. ## Refactoring inline code into a functionThe standings loop in
main.gobuilds each team's report line inline. It callsAverageScores, branches on theokvalue, and prints one of two messages. That works, but the logic is buried inmain, where it can't be tested or reused. Extracting it into a function with clear parameters and one return value is a refactor: the program prints exactly what it did before, but the logic now has a name and a home.The function takes the team's
nameandscoresby value because it only reads them, and it returns astringinstead of printing so the caller decides what to do with the line. ## Acting on a multi-value resultLeadingTeamreturns a name and a total, but nothing uses them yet. A caller has to interpret what comes back. For an empty map the name is""and the total is 0, but a real team can also have a total of 0, so the total can't tell you whether a leader exists. The name can.This is the same discipline as checking
okfromAverageScores: look at the value that signals success before trusting the others.main.goalready callsAnnounceLeader, so once you finish it,go run .shows it working. -
Challenge
Step 6: Run the application
Now that all the tasks are done, run the whole app and watch everything work together. This is the first time the pieces you wrote separately come together as one program.
Start the application
Open the Terminal tab and run:
go run .main.goreplays one Thursday night with the scorekeeper. You should see output like this:Thursday Night Trivia League Scorekeeper Commands " ADD " -> add score "end" -> end round "Quit " -> quit "" -> unknown command: noop "undo" -> unknown command: undo Score checks Comets all at least 40: false Owls all at least 20: true A score of 65 is high, and a score of 12 is low Rounds of 25 points to reach 100: 4 Standings Comets: total 266, average 53.2 Owls: total 96, average 32.0 Quizzards: no scores yet Leader: Comets with 266 points The Owls win a 200 point bonus round Leader: Owls with 296 points No teams on the board yetMatch each section to the code you wrote:
- Commands shows
NormalizeCommandcleaning each typed command beforeRouteCommandmaps it. The blank input becomesnoop, which the router treats as unknown. - Score checks uses
ValidateScores,ClassifyScore, andRoundsToTarget. Comets fail the check because a makeup score of 38 is below 40. - Standings prints one
TeamReportline per team, in the sorted order fromTeamNames. Quizzards have no scores, soAverageScoresreportsfalseand the report says so. - The last lines show
AnnounceLeaderbefore and afterApplyBonusgives the Owls 200 points, then once more for an empty scoreboard. Congratulations on completing the lab!
- Commands shows
About the author
Real skill practice before real-world application
Hands-on Labs are real environments created by industry experts to help you learn. These environments help you gain knowledge and experience, practice without compromising your system, test without risk, destroy without fear, and let you learn from your mistakes. Hands-on Labs: practice your skills before delivering in the real world.
Learn by doing
Engage hands-on with the tools and technologies you’re learning. You pick the skill, we provide the credentials and environment.
Follow your guide
All labs have detailed instructions and objectives, guiding you through the learning process and ensuring you understand every step.
Turn time into mastery
On average, you retain 75% more of your learning if you take time to practice. Hands-on labs set you up for success to make those skills stick.