🌡️ The Go Journey – Building a Temperature Converter in Go
When you’re learning a new language, small but meaningful projects are gold.
They let you practice real input/output, experiment with syntax, and get instant feedback.
For my first practical Go project, I decided to build a temperature converter — the kind of utility that’s simple enough to finish in one sitting, yet rich enough to explore Go’s fundamentals: command-line args, error handling, string manipulation, and unit tests.
Motivation & Setup
A temperature converter is perfect for Go beginners because it touches everything that matters early on:
- Real input/output through the command line.
- Simple arithmetic that’s easy to test.
- Error handling and formatting that show off Go’s clarity.
You’ll need Go 1.22+ (though any recent version will work).
Run your program directly with:
go run main.go 100K
And when you add tests later:
go testThat’s the beauty of Go — batteries included, no third-party runners required.
⚙️ Parsing Command-Line Arguments
Let’s start with user input.
Go exposes command-line arguments through the built-in os.Args slice.os.Args[0] is the program name, and the rest are arguments passed in the terminal.
Here’s the skeleton:
package main
import (
"fmt"
"os"
"strings"
"strconv"
)
func main() {
if len(os.Args) < 2 {
fmt.Println("Usage: go run main.go <temperature><C|F>")
os.Exit(1)
}
input := strings.TrimSpace(strings.ToUpper(os.Args[1]))
unit := input[len(input)-1]
valueStr := input[:len(input)-1]
value, err := strconv.ParseFloat(valueStr, 64)
if err != nil {
fmt.Println("Invalid temperature value.")
os.Exit(1)
}
switch unit {
case 'C':
fmt.Printf("%.2f°C = %.2f°F\n", value, CelciustoFarenheit(value))
case 'F':
fmt.Printf("%.2f°F = %.2f°C\n", value, FarenheitToCelcius(value))
default:
fmt.Println("Unknown unit. Use C or F.")
os.Exit(1)
}
}A few things are happening here:
strings.TrimSpaceremoves accidental spaces.strings.ToUpperlets you handle both “c” and “C”.unit := input[len(input)-1]grabs the last character.valueStr := input[:len(input)-1]slices out everything before it.
Go strings can be indexed like arrays — len(input)-1 safely gets the last byte (in this case, a single-letter ASCII unit).
And as always in Go, you check errors first, not last.
🔢 Numbers & Formatting
Once we have valueStr, we turn it into a number with strconv.ParseFloat.
value, err := strconv.ParseFloat(valueStr, 64)Go doesn’t do automatic conversions — that’s intentional.
The compiler forces you to think about types, which saves you from runtime surprises later.
For output, fmt.Printf makes formatting dead simple:
fmt.Printf("%.2f°C = %.2f°F\n", value, CelciusToFarenheit(value))%.2f keeps your decimals neat, and Go handles Unicode perfectly,
so symbols like °C and °F just work.
Control Flow & Functions
Let’s move conversion logic into small helper functions:
func CelsiusToFahrenheit(c float64) float64 {
return (c * 9 / 5) + 32
}
func FahrenheitToCelsius(f float64) float64 {
return (f - 32) * 5 / 9
}
func CelsiusToKelvin(c float64) float64 {
return c + 273.15
}
func KelvinToCelsius(k float64) float64 {
return k - 273.15
}They’re pure functions — no side effects, no global state — so they’re perfect for testing later.
Then, use a switch statement to branch logic based on the unit:
switch unit {
case 'C':
...
case 'F':
...
}This keeps the code readable and flat — no nested if-else chains.
Testing Culture
Testing is part of Go’s culture — not an afterthought.
Create a file called converter_test.go and write a table-driven test (the Go norm):
func TestCelciustoFarenheit(t *testing.T) {
tests := []struct {
c, want float64
}{
{0, 32},
{100, 212},
}
for _, tt := range tests {
t.Run(fmt.Sprintf("%vC", tt.c), func(t *testing.T) {
got := CtoF(tt.c)
if got != tt.want {
t.Errorf("got %.2f, want %.2f", got, tt.want)
}
})
}
}Here’s what’s cool:
- You define test cases as anonymous structs.
- Loop over them — concise and expressive.
t.Rungives each case its own name in test output.- Each test message is clear (
got vs want).
To run them:
go test and you'll see result:
PASS
ok go-temperature 0.764sThat’s the Go way — fast, self-contained, no setup.🧠 Takeaways & Next Steps
In this project, we touched:
- Go’s packages and imports.
- Command-line args via
os.Args. - Strings, runes, and byte slicing.
- Functions and switch statements.
- Error handling and early exits.
- Unit testing with table-driven tests.
- The Go mindset: explicit, clean, and predictable.
If you want to go further:
- Add inverse conversions (Kelvin → Fahrenheit).
- Validate malformed input (like “100x”).
- Explore Go’s
flagpackage for richer CLI tools. - Format output with colors or units aligned in a table.
And above all — keep writing small, testable programs.
Each one will sharpen your understanding of Go’s design philosophy.
The whole repository you can find here