Go (Golang) is Google's statically typed, compiled, cross-platform, open-source programming language that combines efficiency with ease of use. Its features include built-in concurrency via goroutines and channels. It has simple syntax, fast compilation, and a comprehensive standard library. It's popular for microservices, web servers, and cloud-native development.
Installation guide: Getting Started with Go — Install Go
Module One: Go Core Syntax
- Package declaration and Imports
- Variables and Types
- Control Flow and Loops:
forloops,rangeloops, andswitchstatements
Package declaration and Imports
A Go program is made of packages. Packages in Go are directories containing source files that work together to organize and reuse code. Every Go file belongs to a package, and that package is declared at the top of the file. A package like fmt (for formatted I/O) is one you bring in with an import.
A package can also be defined as a group of files that share one package name, usually in one folder. Every Go file starts with a package line that names it. A Go program starts in a package called main, and its function main is where the program starts running.
A package works like a folder of recipes in a cookbook section. Every recipe card in the folder says which section it belongs to. The section that starts the meal is the one the cook opens first.
// package line
package main
// import line
import "fmt"
func main() {
fmt.Println("Hello, World!")
}
Expected output: Hello, World!
package main tells Go which package the file belongs to. The main package is special because it's the entry package for a program intended to run as an executable.
import "fmt" makes Go's standard library fmt package available to the file. It is used for formatted input and output, including printing text to the terminal.
func main() declares a function named main. When you run an executable program, execution begins in the main function.
fmt.Println("Hello, World!") calls the Println function from fmt to print text, followed by a new line. The dot accesses an exported name from a package, and the parentheses contain the arguments you're passing to the function.
How to Use Code That Belongs to Another Package (Import Another Package)
Our file often needs code from another package, which is listed in the import line. The import statement makes a standard library package available to the file. After importing, you call its functions using the package name, a dot, and the function name, such as fmt.Println. fmt is the package, and Println is the function inside it.
package main
// Makes the standard library package available to the file.
import "fmt"
// Declares the function where execution begins.
func main() {
// Creates a variable named name as a string using the short variable declaration.
name := "Ada"
// Calls fmt.Println, which lives in the fmt package.
fmt.Println("Hello", name)
}
Expected output:
Hello Ada
Naming Conventions in Go
As an example, we have fmt.Println("Train Name"). Under fmt, we have the package name, and under Println, we have the function name.
Note: Println starts with a capital letter. In Go, names beginning with a capital letter are exported and can be accessed from another package, while names beginning with a lowercase letter are unexported and can only be accessed within their own package. Go decides what other packages can use based on the first letter of a name.
Unused Imports and Compiler Errors
Go checks imports strictly. If a file imports a package and doesn't use it, the compiler stops with an error. If the file uses a package without importing it, the compiler also stops. The import block is an honest list that shows what a file depends on, and every imported package must be used.
Never use a package you haven't imported yet. Check whether an imported package is needed before importing it. You don't need to use every function inside an imported package, just one.
Incorrect:
package main
import (
"fmt"
"strings"
)
func main() {
name := "ada"
}
Go complains about fmt and strings being imported and not used. It also complains that name is declared and not used, because Go is just as strict about unused variables.
Correct:
package main
import (
"fmt"
"strings"
)
func main() {
name := "ada"
fmt.Println(strings.ToUpper(name))
}
Go vs. JavaScript
| JavaScript | Go |
|---|---|
const name = "Ada"; |
name := "Ada" |
console.log("Hello", name); |
fmt.Println("Hello", name) |
Lesson Takeaway
- Every Go file starts with a package line, and
package mainwith themainfunction is where a program starts. - An import statement brings in another package, and you can call its functions using the package name.
- A name starting with a capital letter is exported and can be used from other packages, while a lowercase name remains unexported.
- Go returns a compiler error for unused imports.
Variables and Types
A variable is a named storage location in memory used to hold a value of a specific type. It is a named box that holds a value.
In Go, variables are statically typed, meaning their type is known at compile time and cannot change.
Variables are declared using the var keyword, followed by the name, the type, and an optional value. The type says what kind of value the box can hold, and Go checks this. Any value written with the wrong type will be rejected by Go.
Kitchen analogy: There is a labelled jar in the kitchen. The label is the name, the jar's size and purpose are the type, while the contents are the value.
var itemCount int = 3
-
var— keyword -
itemCount— name -
int— type -
3— value
Go also infers the type from the value. If you provide a value, you can leave out the type. In that case, 3 becomes an integer (int), 2.5 becomes a float64, "C" becomes a string, and true or false becomes a boolean.
The short assignment operator (:=) is used to declare a variable in Go, with the type usually inferred from the assigned value. It can only be used inside a function. Variables declared outside a function must use the var keyword.
Use var when you want to specify a type that cannot be inferred from the value, or when you want a zero value, which is the default value Go assigns to a variable when no value is provided. Inside a function, the short assignment operator (:=) is the usual choice for declaring a new variable.
Go vs. JavaScript
| JavaScript | Go |
|---|---|
let item = 3; |
itemCount := 3 |
Type Inference in Go
Type inference is Go working out a variable's type from the value you give it. Go determines a variable's type from the initial value you assign to it. You don't write the type; Go infers it from the value, and the variable has a fixed type. So, the variable can't later hold a different kind of value. For example, if name is declared as a string using the short assignment operator (:=), assigning 5 to name later would be incorrect because name has already been defined as a string.
Kitchen analogy: A shelf is labelled by the first item placed on it. If the first item placed on a shelf is a book, then it can't also be used to store a banana.
// Type Inference in Go
package main
import "fmt"
func main() {
// var declares itemCount with the type int and the value 3.
var itemCount int = 3
// price := 29.5 declares a new variable. Go infers float64 from 29.5.
price := 29.5
// name := "tea" declares a string. The := creates a new name here.
name := "tea"
// A plain = assigns to an existing variable. It does not declare a new one, so no := is needed.
itemCount = itemCount + 6
// Prints the three values in order: name, itemCount, and price.
fmt.Println(name, itemCount, price)
}
Expected output:
tea 9 29.5
How does this compare to TypeScript?
If you're coming from TypeScript, this will feel familiar:
let name = "tea";
name = "coffee"; // Valid
name = 5; // Type error
TypeScript infers that name is a string, much like Go does. The difference is that TypeScript's types disappear once it's compiled to JavaScript, while Go's types are part of the compiled program. Both languages help catch certain mistakes before your program runs. Here's the same thing in Go:
name := "tea"
name = "coffee" // Valid
name = 5 // Compile error
Go Variable Naming Rules
Go variable names can be short, like x and y, or descriptive, like age and price. They must start with a letter or underscore, cannot start with a digit, and can contain only letters, digits, and underscores. Variable names are case-sensitive, cannot contain spaces or be Go keywords, and have no length limit. Multi-word variable names can use camel case (myVariableName), Pascal case (MyVariableName), or snake case (my_variable_name).
Lesson Takeaway
- The var keyword is used to declare a variable in Go.
- The short variable declaration operator (:=) declares a variable with its type inferred from the assigned value.
- The short variable declaration operator (:=) can only be used inside a function.
Control Flow and Loops
A quick note before we start: the snippets in this section leave out package main and import "fmt" to keep things short. If you want to run one, add those two lines at the top of the file.
for Loops
Many languages have a separate keyword for each loop type, but Go doesn't. It has only for, and what follows it decides the loop shape. The header sits between for and the opening brace, and the body is the code that repeats.
Kitchen analogy: A kitchen timer can count down a set number of minutes. When it reaches zero, it stops and is ready to be turned off. One tool, 3 uses.
func main() {
// i := 1 happens once, before any check.
// i <= 3 is checked before every pass, and the body only runs while it's true.
// i++ adds 1 after each pass.
for i := 1; i <= 3; i++ {
fmt.Println(i)
}
}
Expected output:
1
2
3
-
for— keyword -
i := 1— initialises i to 1 -
i <= 3— condition -
i++— increments i by one -
fmt.Println(i)— body, the code that repeats
A typical for loop has three parts, separated by semicolons.
- Initial statement: It runs once before the loop starts and usually creates a counter.
i := 1;
- Condition statement: It is checked before each pass through the loop.
i <= 3;
- Post statement: It runs after each pass and usually changes the counter.
i++
So, i := 1 starts the counter at one, the loop runs while i is less than or equal to three, and i++ adds one after each pass.
Types of For Loops in Go
There are three major types of loops in Go:
- Classic loop
- Condition only loop
- Infinite loop
1. Classic Loop
We use a classic loop when we want to repeat something a known number of times or track a counter.
for i := 0; i < 5; i++ {
fmt.Println(i)
}
A classic loop has three parts: the initial statement, condition statement, and post statement. It runs five times in this example, printing 0 to 4.
2. Condition Only Loop
This works like a while loop in JavaScript or TypeScript.
count := 0
for count < 5 {
fmt.Println(count)
count++
}
Note: There's no semicolon in the loop header. It has only a condition and runs while the condition is true.
3. Infinite Loop
You use an infinite loop when you omit the condition. The loop continues indefinitely unless something inside stops it, usually with the keyword break or return.
for {
fmt.Println("Still running")
break
}
Note: break stops the loop after the first print. An infinite loop has no condition and runs indefinitely until it is stopped.
Go vs. JavaScript/TypeScript
| Purpose | Go | JavaScript |
|---|---|---|
| Counter loop | for i := 0; i < 3; i++ { } |
for (let i = 0; i < 3; i++) { } |
| Condition loop | for count < 3 { } |
while (count < 3) { } |
| Infinite loop | for { } |
while (true) { } |
Main difference: Go does not have a separate while keyword. It uses for for all loop types, and what follows for determines how the loop behaves.
Range Loop
Writing i := 0; i < 5; i++ can honestly be a bit long sometimes. So Go gives us a shorter way to write it using range.
for i := range 5 {
fmt.Println(i)
}
This gives i values from 0 to 4, printing each value on a new line.
Expected output:
0
1
2
3
4
Note: range 5 iterates five times, producing values from 0 up to, but not including, 5. Ranging over a number like this needs Go 1.22 or newer, so if it doesn't compile, run go version to check what you have installed.
In a loop, continue skips the rest of the current iteration and moves to the next one, while break stops the whole loop.
func main() {
for i := range 5 {
if i == 2 {
continue
}
fmt.Println(i)
}
}
Expected output:
0
1
3
4
Lesson Takeaway
- Go has only one looping keyword,
for. - A classic loop has three parts, separated by semicolons: an initialisation statement that starts the counter, a condition statement that is checked before every pass, and a post statement that runs after every pass.
- A condition-only loop runs as long as the condition is true.
- An infinite loop runs indefinitely unless it is stopped using
breakorreturn. - A range loop provides a shorter way to iterate over values, such as
for i := range 5, which produces values from 0 to 4. -
continueskips the rest of the current iteration and moves to the next one, whilebreakstops the loop.
Route Logic Using Typed Switch Statements
In JavaScript or TypeScript, there is a switch statement or an if-else statement. Go has both too. A switch statement in Go selects and executes one block of code from multiple possibilities based on the value or type of an expression. A switch statement provides a cleaner alternative to multiple if statements. It also supports both value-based and type-based branching.
How does it work?
A switch statement tells Go which values to examine. Then, each case defines a possible value. Go runs the code under the matching case. If no case matches, it runs default.
package main
import "fmt"
func main() {
day := "Monday"
switch day {
case "Monday":
fmt.Println("Start of the week")
case "Friday":
fmt.Println("Almost the weekend")
default:
fmt.Println("Another day")
}
}
Expected output:
Start of the week
Go supports two types of switch statements
- Expression switch
- Type switch
1. An Expression Switch
An expression switch evaluates an expression and executes the matching case block. This is useful when you have several possible options.
// Expression switch: evaluate the value, find a match,
// execute the matching case, and exit the switch.
func main() {
route := "/profile"
switch route {
case "/":
fmt.Println("Home page")
case "/dashboard":
fmt.Println("Dashboard page")
case "/profile":
fmt.Println("Profile page")
default:
fmt.Println("Page not found")
}
}
An expression switch can also check more than one value in a single case. Just separate the values with a comma.
func main() {
age := 25
switch age {
case 18:
fmt.Println("Finally an adult")
case 25, 30: // two values, one case
fmt.Println("How's life?")
case 35:
fmt.Println("Oldie")
default:
fmt.Println("Another age")
}
}
Expected output:
How's life?
The value in the switch and the values in the case expressions must be comparable under Go's type rules. E.g., an integer variable cannot be compared directly against a string.
func main() {
age := 25
switch age {
case "25": // Error: cannot compare int with string
fmt.Println("Twenty-five")
}
}
This code produces a compile-time error because age is an integer (int), while "25" is a string. Go does not allow these incompatible types to be compared directly in a switch statement. The fix is to make both sides the same type, like case 25:.
Go also allows you to omit the value after switch. Each case can then contain a Boolean condition.
func main() {
score := 75
switch {
case score >= 80:
fmt.Println("Excellent")
case score >= 60:
fmt.Println("Passed")
default:
fmt.Println("Try again")
}
}
Go evaluates the cases from top to bottom and executes the first case whose condition is true.
2. Type Switch
A switch checks the value, while a type switch checks what type a value is. This is useful when a variable has an interface type, such as any, and the value inside can be any data type. In Go, any is an alias for the empty interface, and it can hold a value of any type.
func main() {
var value any = true
switch v := value.(type) {
case string:
fmt.Println("This is a string:", v)
case int:
fmt.Println("This is an integer:", v)
case bool:
fmt.Println("This is a Boolean:", v)
default:
fmt.Println("Unknown type")
}
}
-
value— a variable of type any. -
.(type)— the syntax used to inspect the value's type in a type switch. -
v— a variable declared in the switch that refers to the value inside each case, with its specific type. -
case string— when the value is a string, v is a string. -
case int— when the value is an integer, v is an integer.
Expression Switch vs. Type Switch
| Feature | Expression switch | Type switch |
|---|---|---|
| What it checks | A value or Boolean condition | The type of a value |
| Typical use | Routes, selectors, and codes | Values whose types may differ |
| Example | switch route |
switch v := value.(type) |
| Cases | Values or Boolean conditions | Types such as string, int, etc. |
| Matching | Executes the matching case | Executes the case matching the value's type |
| Type handling | Cases must be compatible with the switch expression's type | Go identifies the value's type in each case |
Go vs. JavaScript
| Feature | Go | JavaScript |
|---|---|---|
| Syntax | switch expression { } |
switch (expression) { } |
| Case behaviour | Exits the switch automatically after a matching case (use fallthrough if you want it to continue) |
Requires break to prevent fall-through |
Lesson Takeaway
- Use an expression switch when one value can have several possible outcomes.
- Use a type switch when you need to identify a value's type.
- Use
caseto define options anddefaultto handle anything unmatched.
Final Practice Exercise
Create a small program that combines what you've learned.
Your program should:
- Declare a variable called
statusand assign it"pending". - Use an expression switch to print a message for
"pending","approved", or"rejected". - Print
"Unknown status"for any other value. - Use a
forloop withrange 5to print the numbers zero through four, but skip the number two usingcontinue.
Next Module: Data Structures

Top comments (0)