Featured resource
2026 Tech Forecast
2026 Tech Forecast

1,500+ tech insiders, business leaders, and Pluralsight Authors share their predictions on what’s shifting fastest and how to stay ahead.

Download the forecast
  • Lab
    • Libraries: If you want this lab, consider one of these libraries.
    • Core Tech
Labs

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 platform
Lab Info
Level
Beginner
Last updated
Nov 04, 2026
Duration
45m

Contact sales

By clicking submit, you agree to our Privacy Policy and Terms of Use, and consent to receive marketing emails from Pluralsight.
Table of Contents
  1. 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 add twice 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 // TODO comment and a // Hint line. 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/else and switch with a fallback branch, so the router never chokes on an unexpected command.
    • Step 3 teaches the three for loop shapes: the infinite loop with break, the counter-based loop, and the loop with only a condition.
    • Step 4 teaches range over 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.go into a function.

    Run go test . -count=1 at any point to see which TODOs still need work. Move on to Step 2 when you're ready to make the router talk back.

  2. 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, or quit, sometimes with stray capitalization or spaces. Go's switch statement, with a default case, is the cleanest way to turn a small set of known strings into actions while still handling everything else safely.

    Open command_router.go and look at RouteCommand. For now it returns an empty string. You'll write a switch whose cases map recognized commands to actions, plus a default fallback for anything else. ## Falling back gracefully on ranges of values

    A switch is great for exact-match strings, but classifying a number into buckets like "low" or "high" reads more naturally as an if/else chain, where each branch narrows the range and the final else catches 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 switch

    RouteCommand's default case 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.

  3. 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 for loop without any condition (for { ... }) runs forever until something inside the block explicitly breaks 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 scores

    Makeup rounds are different: the host already knows exactly how many scores they're entering. That's the classic case for a counter-based for loop, 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 loop

    Counter-based loops aren't just for collecting. They're also useful for filtering, using continue to 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 holds

    There is a third for shape. Writing for condition { ... } repeats the block for as long as the condition is true, which is what other languages call a while loop. 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.

  4. 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 matters

    Team 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.

  5. 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 once

    A 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 once

    Finding 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 same return statement. No wrapper struct is required for a result this simple. ## Changing a caller's value through a pointer

    Every 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 function

    The standings loop in main.go builds each team's report line inline. It calls AverageScores, branches on the ok value, and prints one of two messages. That works, but the logic is buried in main, 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 name and scores by value because it only reads them, and it returns a string instead of printing so the caller decides what to do with the line. ## Acting on a multi-value result

    LeadingTeam returns 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 ok from AverageScores: look at the value that signals success before trusting the others. main.go already calls AnnounceLeader, so once you finish it, go run . shows it working.

  6. 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.go replays 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 yet
    

    Match each section to the code you wrote:

    • Commands shows NormalizeCommand cleaning each typed command before RouteCommand maps it. The blank input becomes noop, which the router treats as unknown.
    • Score checks uses ValidateScores, ClassifyScore, and RoundsToTarget. Comets fail the check because a makeup score of 38 is below 40.
    • Standings prints one TeamReport line per team, in the sorted order from TeamNames. Quizzards have no scores, so AverageScores reports false and the report says so.
    • The last lines show AnnounceLeader before and after ApplyBonus gives the Owls 200 points, then once more for an empty scoreboard. Congratulations on completing the lab!
About the author

Pluralsight’s AI authoring technology is designed to accelerate the creation of hands-on, technical learning experiences. Serving as a first-pass content generator, it produces structured lab drafts aligned to learning objectives defined by Pluralsight’s Curriculum team. Each lab is then enhanced by our Content team, who configure the environments, refine instructions, and conduct rigorous technical and quality reviews. The result is a collaboration between artificial intelligence and human expertise, where AI supports scale and efficiency, and Pluralsight experts ensure accuracy, relevance, and instructional quality, helping learners build practical skills with confidence.

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.

Get started with Pluralsight