Your First Go Program
Every Go journey starts with a small program that compiles, runs, and prints output.
Introduction
Every Go journey starts with a small program that compiles, runs, and prints output. Understanding package main, import paths, and the func main() entry point is essential for reading production codebases at companies like Stripe and Datadog.
Unlike languages with classes, Go organizes code into packages. The main package is special — it produces an executable. Other packages are libraries. This lesson shows the anatomy of a Go file so you are not confused when opening a payment service repository with dozens of packages.
You will compile with go build, run with go run, pass command-line arguments via os.Args, and interpret exit codes — the same flow used in Docker HEALTHCHECK scripts and CI smoke tests.
The story
An SRE at a fintech company writes a nightly cron job that reconciles payment gateway logs: ./reconcile --date=2026-03-15. If the operator forgets the date flag, the program must print usage to stderr, exit with code 1, and leave a clear audit trail — the same contract Kubernetes Jobs and GitHub Actions expect from CLI tools.
Understanding package main, os.Args, and exit codes is how you read real infrastructure repos — not just tutorial snippets with hard-coded values.
Understanding the topic
Key concepts
- package main declares an executable; other package names create libraries.
- import groups standard library and third-party packages; unused imports are compile errors.
- func main() is the entry point — no parameters, no return value.
- Exported identifiers start with uppercase; unexported with lowercase (package visibility).
- go build produces a binary named after the directory; -o flag sets output path.
- os.Args is a []string where index 0 is the program name.
flowchart TDStart([main]) --> Init[Package init]Init --> Logic[Business Logic]Logic --> Output[fmt.Println]Output --> End([Exit 0])
Step-by-step explanation
- Write source in .go files; all files in a directory share the same package name.
- go build compiles packages; errors show file:line with clear messages.
- go run compiles to a temp binary, runs it, and deletes it.
- Args pass from shell to os.Args slice.
- os.Exit(code) sets exit code for scripting; defer runs before exit.
- Debug in IDE with breakpoints — delve or IDE debugger attaches to the binary.
Practical code example
A complete first program with imports, args handling, and structured output:
package mainimport ("fmt""os")const appName = "TechLearningPro CLI"func main() {if len(os.Args) < 2 {fmt.Fprintf(os.Stderr, "Usage: %s <name>\n", os.Args[0])os.Exit(1)}name := os.Args[1]fmt.Printf("[%s] Hello, %s! Welcome to Go.\n", appName, name)}
Output
usage: reconcile --date=YYYY-MM-DD
Line-by-line code explanation
package mainmarks this file as part of an executable, not a reusable library.import ("fmt"; "os")groups standard-library imports for formatting and OS interaction.func main()is where execution begins — no classes, no inheritance.if len(os.Args) < 2checks whether the user supplied a required argument.fmt.Fprintln(os.Stderr, "usage: ...")writes help text to the error stream, not stdout.os.Exit(1)signals failure to shells, cron, and CI pipelines checking exit codes.date := os.Args[1]reads the first user-supplied argument after the program name.fmt.Printf("Reconciling %s\n", date)prints a confirmation with formatted output.os.Exit(0)is implicit at the end ofmainwhen no error occurs.
Key takeaway: os.Exit skips deferred functions in main — use return from main when possible. Fprintf to os.Stderr separates errors from stdout for Unix pipelines.
Real-world use
Where you'll use this in production
- CLI tools for DevOps automation and CI/CD pipeline scripts.
- Docker HEALTHCHECK binaries that exit 0 on success.
- Migration and seed scripts run via kubectl exec or systemd.
- Developer productivity tools: code generators, linters, and scaffolding CLIs.
Best practices
- Use fmt.Fprintf(os.Stderr, ...) for usage messages and errors.
- Return exit code 1 (or higher) on failure; 0 on success.
- Keep main thin — delegate to internal packages for testability.
- Use constants for app name and version strings.
- Add -h flag with flag package for user-facing CLIs.
Common mistakes
- Forgetting package main — library packages cannot produce executables.
- Leaving unused imports — Go rejects them at compile time.
- Using println (lowercase) instead of fmt.Println — println is for bootstrap only.
- Calling os.Exit inside defer-heavy code — defers won't run.
- Hardcoding paths instead of using os.Args and flag package.
Advanced interview questions
Q1BeginnerWhat is the entry point of a Go program?
Q2BeginnerWhat does os.Exit(1) signify?
Q3IntermediateDifference between go run and go build?
Q4IntermediateHow are command-line arguments passed?
Q5AdvancedTrace execution from go run to main.
Summary
package main + func main() is the executable entry point. go build creates binaries; go run compiles and executes immediately. os.Args and flag package handle CLI arguments professionally. Keep main thin; push logic into testable internal packages. Next lesson: variables, types, and Go's type system fundamentals.