DEV Community

Adetola Esther Aderibigbe
Adetola Esther Aderibigbe

Posted on

GO: Introduction and core syntax

Golang image

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: for loops, range loops, and switch statements

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!")
}
Enter fullscreen mode Exit fullscreen mode

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)
}
Enter fullscreen mode Exit fullscreen mode

Expected output:

Hello Ada
Enter fullscreen mode Exit fullscreen mode

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"
}
Enter fullscreen mode Exit fullscreen mode

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))
}
Enter fullscreen mode Exit fullscreen mode

Go vs. JavaScript

JavaScript Go
const name = "Ada"; name := "Ada"
console.log("Hello", name); fmt.Println("Hello", name)

Lesson Takeaway

  1. Every Go file starts with a package line, and package main with the main function is where a program starts.
  2. An import statement brings in another package, and you can call its functions using the package name.
  3. A name starting with a capital letter is exported and can be used from other packages, while a lowercase name remains unexported.
  4. 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
Enter fullscreen mode Exit fullscreen mode
  • 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)
}
Enter fullscreen mode Exit fullscreen mode

Expected output:

tea 9 29.5
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)
    }
}
Enter fullscreen mode Exit fullscreen mode

Expected output:

1
2
3
Enter fullscreen mode Exit fullscreen mode
  • 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.

  1. Initial statement: It runs once before the loop starts and usually creates a counter.
i := 1;
Enter fullscreen mode Exit fullscreen mode
  1. Condition statement: It is checked before each pass through the loop.
i <= 3;
Enter fullscreen mode Exit fullscreen mode
  1. Post statement: It runs after each pass and usually changes the counter.
i++
Enter fullscreen mode Exit fullscreen mode

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)
}
Enter fullscreen mode Exit fullscreen mode

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++
}
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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)
}
Enter fullscreen mode Exit fullscreen mode

This gives i values from 0 to 4, printing each value on a new line.

Expected output:

0
1
2
3
4
Enter fullscreen mode Exit fullscreen mode

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)
    }
}
Enter fullscreen mode Exit fullscreen mode

Expected output:

0
1
3
4
Enter fullscreen mode Exit fullscreen mode

Lesson Takeaway

  1. Go has only one looping keyword, for.
  2. 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.
  3. A condition-only loop runs as long as the condition is true.
  4. An infinite loop runs indefinitely unless it is stopped using break or return.
  5. A range loop provides a shorter way to iterate over values, such as for i := range 5, which produces values from 0 to 4.
  6. continue skips the rest of the current iteration and moves to the next one, while break stops 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")
    }
}
Enter fullscreen mode Exit fullscreen mode

Expected output:

Start of the week
Enter fullscreen mode Exit fullscreen mode

Go supports two types of switch statements

  1. Expression switch
  2. 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")
    }
}
Enter fullscreen mode Exit fullscreen mode

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")
    }
}
Enter fullscreen mode Exit fullscreen mode

Expected output:

How's life?
Enter fullscreen mode Exit fullscreen mode

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")
    }
}
Enter fullscreen mode Exit fullscreen mode

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")
    }
}
Enter fullscreen mode Exit fullscreen mode

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")
    }
}
Enter fullscreen mode Exit fullscreen mode
  • 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

  1. Use an expression switch when one value can have several possible outcomes.
  2. Use a type switch when you need to identify a value's type.
  3. Use case to define options and default to handle anything unmatched.

Final Practice Exercise

Create a small program that combines what you've learned.

Your program should:

  1. Declare a variable called status and assign it "pending".
  2. Use an expression switch to print a message for "pending", "approved", or "rejected".
  3. Print "Unknown status" for any other value.
  4. Use a for loop with range 5 to print the numbers zero through four, but skip the number two using continue.

Next Module: Data Structures

Top comments (0)