- Lab
-
Libraries: If you want this lab, consider one of these libraries.
- Core Tech
Structured Data in Go
Globomantics loads pallets onto delivery trucks every morning and needs a manifest tool to track what is loaded where. You are building that tool: truck bins are fixed in number, shipment queues grow as pallets arrive, and each pallet is a record keyed by ID inside a warehouse zone map. The dispatcher needs the tool to report accurate totals without one part of the program accidentally corrupting another part's copy of the data.
Lab Info
Table of Contents
-
Challenge
Step 1: Orient yourself with the manifest tool scenario and toolchain
Globomantics needs a manifest tool
Globomantics loads pallets onto delivery trucks every morning, and right now nobody has a reliable way to see what is loaded where. Throughout this lab, you will build a small command-line manifest tool in Go that models three kinds of data the dispatcher cares about: a fixed set of truck bins, a growing queue of shipment pallets, and a warehouse of pallet records keyed by ID. Each step adds one file to a shared module, and the last step wires all three into a single printed report.
You will write real Go source across seven files:
main.go(the entry point),values.go(constants and pointer semantics),bins.goandshipments.go(arrays and slices),pallet.goandwarehouse.go(structs and maps), andmanifest.go(the final report). Every file already exists as a starter stub with// TODOmarkers; you fill in the marked functions, you never create new files. ## Confirm your toolchain before writing codeBefore touching the manifest files, make sure your editor and Go installation agree on the same version this lab expects.
- Look over the lab's editor: each source file you will change opens as its own tab, and the Terminal tab gives you a shell for running Go commands.
- Open a terminal in the workspace root and run
go version. It must reportgo1.26. If it reports something else, your PATH is pointing at a different install and the graded checks will not match what you see locally. - Run
go env GOMODfrom the workspace root. It should print the path to this workspace'sgo.mod. If it prints nothing, your terminal is not sitting in the workspace root;cdthere before continuing.
Once
go versionandgo env GOMODboth report correctly, you are ready to start editing files in Step 2. ## How the pieces fit togetherKeep this map in mind as you move through the steps:
- Step 2 gives you a runnable entry point and the constants describing truck capacity and pallet weight, plus your first look at how Go tells a value copy apart from a pointer-based update.
- Step 3 adds a fixed array of bin totals and a growable slice of shipment weights, then shows you the sharp edge of slicing: two slices can secretly point at the same backing storage.
- Step 4 models each pallet as a struct and stores pallets by ID in a map, then forces you to write a real deep clone so a copy of the warehouse can't corrupt the original.
- Step 5 combines everything into one
Manifesttype with a total-weight calculation and a formatted report, called from themainyou wrote in Step 2.
Each task you complete replaces the
// TODOmarkers in one file. Nothing you write is thrown away: when you run the program in Step 6,maincalls nearly every function you built. Only the three demonstrationsIncreaseWeightCopy,BinView, andMutateSubSliceare exercised solely by their checks. -
Challenge
Step 2: Build the manifest entry point and core values
Every Go program starts at main
A Go module's executable entry point is the
mainfunction insidepackage main. When you rungo run .or build the module, Go finds this function and runs it first, nothing else executes on its own. Printing output frommainis the job of thefmtpackage:fmt.Printlnwrites a line to standard output followed by a newline, which is exactly what the dispatcher's manifest report needs for its header.main.goin this workspace already declarespackage mainand importsfmt; the rest ofmain, beneath the header's TODO, already calls the helper functions you will implement in later tasks, so you only add the header line itself. ## Constants, variables, and formatted stringsGo distinguishes constants (
const), which are fixed at compile time and can never change, from variables (var), which can be reassigned while the program runs. Truck capacity is a good fit for a constant, since it never changes for a given truck model, while a default pallet weight might reasonably vary, so it lives in a variable.values.godeclares both for you already.To combine multiple values into one readable line,
fmt.Sprintfworks likePrintlnbut returns the formatted string instead of printing it, using%d(or%v) placeholders for the values you pass in. ## Value copies versus pointer updatesWhen you pass an
intto a Go function, the function receives its own independent copy, changing that copy inside the function has no effect on the caller's variable. A pointer, written*int, instead holds the memory address of the caller's variable, so writing through it with*ptr = newValue(or*ptr += delta) changes the original.This distinction matters constantly in the manifest tool: whenever one part of the program needs to update a value another part is holding onto, it needs a pointer, not a copy.
values.godeclares both versions for you:IncreaseWeightCopytakes a plainintandIncreasePalletWeighttakes an*int. You supply the bodies. -
Challenge
Step 3: Track bins and shipments with arrays and slices
Fixed arrays for a fixed number of bins
Every Globomantics truck has exactly four cargo bins; that number never changes, so Go's fixed-size array type, written
[4]int, is the right fit. An array's length is part of its type:[4]intand[5]intare different types entirely, and the length can never grow or shrink at runtime. Array values are also copied whole when assigned or returned, which is why returning an array by value gives the caller an independent snapshot rather than a shared reference.bins.godeclaresInitializeBins, which returns[4]int, andBinView, which takes a pointer to that array. Slicing an array withbins[:]produces a slice that points at the array's own storage instead of copying it, a first look at the shared-storage behavior Task 3.3 returns to. ## Slices grow where arrays can'tUnlike bins, the number of shipment pallets arriving each morning is unpredictable, so
shipments.gotracks pallet weights in a slice,[]int, instead of an array. A slice is a small header (pointer, length, capacity) pointing at an underlying array, and Go's built-inappendfunction adds an element, growing the underlying array and copying to a new one behind the scenes whenever the current one runs out of room.Because
appendcan return a slice backed by a different array than the one you passed in, you must always use the sliceappendreturns, discarding it silently drops the added element from the caller's point of view. ## When two slices share one backing arrayHere is the sharp edge: slicing an existing slice with
s[low:high]does not copy anything. The result is a new slice header pointing at the same backing array ass, just with a different starting point and length. Mutating an element through either slice changes the data both slices see, because there is only ever one array underneath.This is a common source of bugs when code assumes slicing behaves like copying. In
shipments.goyou will deliberately trigger this behavior to see it firsthand, so you recognize it later when it happens by accident. -
Challenge
Step 4: Model warehouse records with structs and maps
Structs model one pallet's fields together
A Go
structgroups related fields under one type, similar to a class with only data and no methods (yet).pallet.godeclares aPalletstruct with anID, aZone, aWeight, and aTags []stringfield for notes like "fragile" or "refrigerated". Building every pallet through one constructor function, rather than writing struct literals all over the codebase, keeps that shape consistent.Because
Tagsis itself a slice, the same shared-backing-array behavior you just saw with shipments applies here too, something you'll need to remember again in the next block. ## Maps index records by keyA Go
map[string]Palletassociates each pallet's ID with its full record, giving constant-time lookup by key instead of scanning a slice. Writing to a map uses index syntax,m[key] = value, and writing to a key that already exists simply overwrites the previous value; there is no separate "insert vs update" step to worry about.warehouse.godeclares the warehouse asmap[string]Pallet, keyed by each pallet'sIDfield, so any part of the program can find a pallet's record without searching. ## Why a map copy isn't a deep cloneAssigning one map variable to another,
clone := original, does not copy the map's contents; both variables end up pointing at the same underlying table, so writes through either one are visible through both. Even if you copy entry-by-entry into a brand-new map, eachPalletvalue you copy still has aTagsfield, and copying a struct copies its slice header, not the slice's contents; so both copies would still share oneTagsbacking array.A true deep clone has to walk two levels: a new map, and inside it, a new slice for every
Tagsfield.warehouse.goneeds exactly that. -
Challenge
Step 5: Wire the manifest report together
One type to hold the whole manifest
manifest.godeclares aManifeststruct with three fields:Bins [4]int,Shipments []int, andWarehouse map[string]Pallet, exactly the three data shapes you built in Steps 3 and 4. A constructor function that takes each piece as a parameter and assembles oneManifestvalue keeps the assembly logic in one place, so nothing downstream has to know how the three pieces were built. ## Methods attach behavior to a typeA Go method is a function with a receiver, such as
func (m Manifest) TotalWeight() intattachesTotalWeightto everyManifestvalue, callable asm.TotalWeight(). Because the receiver here is a value, not a pointer, the method readsm's fields without risk of accidentally modifying the caller's manifest.Totaling the manifest means walking all three fields (bins, shipments, and every pallet currently sitting in the warehouse) and adding every weight into one running sum. ## Turning data into a report a human reads
The dispatcher doesn't want to inspect Go values in a debugger; they want a printed report.
fmt.Sprintfwith\nbetween segments builds a multi-line string entirely in memory, whichmain.gothen prints in one call as the last part of its output. This is the same pattern you used forTruckCapacityReportin Step 2, just combining more values: the manifest's total weight, its bin count, and its shipment count.Once this function returns real data, running the program produces the complete manifest end to end.
-
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 .You should see the header, the capacity line, the draft-clone check, and then the manifest report:
=== Globomantics Manifest === Truck capacity: 2800 lbs, pallet weight: 150 lbs Draft P1: 175 lbs [inspected]; warehouse P1: 150 lbs [fragile] Total Weight: 2065 Bins: 4 Shipments: 2The draft line shows the deep clone at work: the draft copy of pallet P1 was reweighed through a pointer and retagged, while the warehouse record kept its original weight and tag. The total of 2065 pounds stays under the truck's 2800-pound capacity.
The program runs in the terminal and prints its output there. Every behavior it shows is code you wrote in the previous steps, so read the output against what each task asked for. That is the whole lab, working as one program. Congratulations on completing the lab!
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.