DEV Community

Cover image for Go Programming Language : Basics
Saksham Malhotra
Saksham Malhotra

Posted on

Go Programming Language : Basics

Go (or Golang) is a statically typed, compiled programming language made by Google. It's popular for its simplicity, speed, and built-in support for concurrency.

This guide covers Go basics with examples so we can easily come back and revise together.

Let's dive into Go—the language powering the cloud!


Variables and Constants

Go requires you to declare variables with specific types, making sure everything is checked at compile time. You can declare them in different ways, with or without initial values.

func main() {
    // Variable with type inference
    var message = "Hello, Go!"

    // Short variable declaration (inside functions only)
    count := 42

    // Constant declaration
    const PI = 3.14

    // Zero value (false for bool)
    var ready bool

    // Print all values
    fmt.Println(message, count, PI, ready)

    // Explicit type conversion (int to float64)
    fmt.Println("Type conversion:", float64(count)/7)
}
Enter fullscreen mode Exit fullscreen mode

Key points

  1. Variables always have a default _"zero" value: 0, "", false, or nil _(for pointers, slices, maps, etc.).
  2. Constants are fixed values set at compile time. They can only be numbers, strings, or booleans.
  3. Go _doesn't do automatic type conversion. _You must convert types explicitly using Type(value) to avoid hidden bugs.

Control Structures

Go provides familiar control structures with clean, minimal syntax.

A key design choice is that Go requires curly braces {} even for single-line blocks, but does not require parentheses () around conditions.

Simple for loop

for i := 0; i < 3; i++ {  
     fmt.Println("Iteration:", i)  
}
Enter fullscreen mode Exit fullscreen mode

for loop used as a while loop

counter := 0  
for counter < 3 {  
    fmt.Println("Counter:", counter)  
    counter++  
 }
Enter fullscreen mode Exit fullscreen mode

Infinite loop with break

sum := 0  
for {  
    sum++  
    if sum > 3 {  
        break  
    }  
    fmt.Println("Sum:", sum)  
}
Enter fullscreen mode Exit fullscreen mode

if-else statement with inline initialization

if grade := 85; grade >= 70 {  
    fmt.Println("You passed with a grade of", grade)  
} else if grade >= 60 {  
     fmt.Println("You barely passed with a grade of", grade)  
} else {  
     fmt.Println("You failed with a grade of", grade)  
}
Enter fullscreen mode Exit fullscreen mode

switch statement

role := "admin"  
switch role {  
case "guest":  
    fmt.Println("Limited access granted")  
case "user":  
    fmt.Println("Standard access granted")  
case "admin":  
    fmt.Println("Full access granted")  
default:  
    fmt.Println("No access")  
}
Enter fullscreen mode Exit fullscreen mode

switch with time.Weekday()

switch time.Now().Weekday() {  
case time.Saturday, time.Sunday:  
    fmt.Println("It's the weekend!")  
default:  
    fmt.Println("It's a weekday.")  
}
Enter fullscreen mode Exit fullscreen mode

Key Points

  • for is the only loop in Go — but it's flexible enough to act like a C-style for, while, or an infinite loop.
  • if can include setup code before the condition — great for things like error checks.
  • switch is powerful and clean:
    • No fallthrough by default (no break needed).
    • Cases can be expressions, not just constants.
    • One case can match multiple values.
    • switch without an expression means switch true.
  • Go supports goto, but use it only when absolutely needed (like breaking out of nested loops).

Data Structures: Arrays and Slices

Go has built-in data structures like arrays and slices.

  • An array is a fixed-size collection of elements all of the same type, and its size is part of its type — so you can’t resize it.
  • A slice is a more flexible, resizable version of an array, and it's the most commonly used sequence type in Go.

Do slices in Go have different types of elements?

No, slices in Go must contain elements of the same type. If you need mixed types, you can use a slice of empty interfaces ([]interface{}),

but that's generally avoided unless truly needed.

Arrays

  • Declared as: var scores [4]int
  • Assigned as: scores[0] = 32
  • Initialized with values: grades := [3]int{98, 93, 87}
  • Letting the compiler count elements: colors := [...]string{"red", "green", "blue"}

Slices

  • Declared as: var names []string
  • Assigned with values: names = []string{"Alice", "Bob"}
  • Initialized directly: primes := []int{2, 3, 5, 7}
  • Using ... is not allowed in slice literals (only in arrays)
  • Appending to a slice:
    • Single: names = append(names, "Charlie")
    • Multiple: numbers = append(numbers, 4, 5)
  • Slice from an array:
  arr := [5]int{10, 20, 30, 40, 50}
  slice := arr[1:4] // [20 30 40]
Enter fullscreen mode Exit fullscreen mode

Key Points

  1. Arrays in Go are value types — not pointers like in some other languages.
  2. Assigning an array or passing it to a function results in a full copy of the array.
  3. Slices hold a reference to the underlying array. Copying a slice points to the same data.
  4. The append function may create a new underlying array if capacity is exceeded.
  5. Slicing (e.g., s[1:4]) creates a new slice pointing to the same array, so changes affect the original.
  6. The zero value of a slice is nil, with 0 length and 0 capacity.

Data structures: Maps

Maps in Go are the equivalent of hash tables or dictionaries in other languages. They provide an unordered collection of key-value pairs, where each key is unique.

Nil map

// Nil map
var empty map[string]int
fmt.Println("Empty map:", empty, "Nil?", empty == nil) 
Enter fullscreen mode Exit fullscreen mode

Creating and adding data to a map

// Map with make and adding data
scores := make(map[string]int)
scores["Alice"], scores["Bob"], scores["Charlie"] = 92, 85, 79
fmt.Println("Scores:", scores)
Enter fullscreen mode Exit fullscreen mode

Map literal

// Literal map
pop := map[string]int{"New York": 8419000, "Los Angeles": 3980000, "Chicago": 2716000}
fmt.Println("Population:", pop)
Enter fullscreen mode Exit fullscreen mode

Access with existence check

// Access with check
if score, ok := scores["Bob"]; ok {
    fmt.Println("Bob's score:", score)
} else {
    fmt.Println("Bob's score not found")
}
Enter fullscreen mode Exit fullscreen mode

Delete a key

// Delete a key
delete(scores, "Charlie")
fmt.Println("After deletion:", scores)
Enter fullscreen mode Exit fullscreen mode

Iterating over a map

// Iterate map
fmt.Println("City populations:")
for city, p := range pop {
    fmt.Printf("%s: %d\n", city, p)
}
Enter fullscreen mode Exit fullscreen mode

Comparing maps

// Compare maps
c1 := map[string]int{"New York": 8419000, "Los Angeles": 3980000}
c2 := map[string]int{"Los Angeles": 3980000, "New York": 8419000}
fmt.Println("Maps equal?", maps.Equal(c1, c2))
Enter fullscreen mode Exit fullscreen mode

Key points

  • Maps are reference types; modifying one variable affects all references.
  • The zero value of a map is nil. A nil map causes a runtime panic when adding keys.
  • Map lookups return two values: the value and a boolean for existence.
  value, ok := myMap[key]
Enter fullscreen mode Exit fullscreen mode
  • The order of iteration over a map is not guaranteed and can change between executions.

Functions

Functions are first-class citizens in Go. They can be passed as arguments, returned from other functions, and assigned to variables. Go functions can return multiple values, making error handling more explicit.

Basic Call


func greet(name string) string {
    return "Hello, " + name
}
greeting := greet("Alice")
Enter fullscreen mode Exit fullscreen mode

Multiple Return Values with Error Handling

func divide(a, b int) (int, error) {
    if b == 0 {
        return 0, fmt.Errorf("cannot divide by zero")
    }
    return a / b, nil
}
// Inside main function
result, err := divide(10, 2)
if err != nil {
    fmt.Println(err)
} else {
    fmt.Println("Result:", result)
}
Enter fullscreen mode Exit fullscreen mode

Named return values

// Function Definition
func calculate(a, b int) (sum, diff int) {
    sum = a + b
    diff = a - b
    return
}
// Inside main function
s, d := calculate(5, 3)
Enter fullscreen mode Exit fullscreen mode

Variadic functions

// Function Definition
func sum(values ...int) int {
    total := 0
    for _, v := range values {
        total += v
    }
    return total
}
// Inside main function
result := sum(1, 2, 3, 4)
Enter fullscreen mode Exit fullscreen mode

Higher-order function

func applyOperation(a, b int, op func(int, int) int) int {
    return op(a, b)
}
// Inside main function
add := func(x, y int) int { return x + y }
result := applyOperation(5, 3, add)
Enter fullscreen mode Exit fullscreen mode

Key points

  • Multiple Return Values: Enables cleaner error handling patterns. The convention is to return the error as the last value.

  • Named Return Values: Parameters in the return type can be named and used as variables within the function, improving readability, especially for complex functions.

  • Variadic Functions: The ... syntax allows a function to accept any number of arguments of a specified type. These arguments are received as a slice inside the function.

  • Functions as First-Class Citizens: Functions can be:

    • Assigned to variables
    • Passed as arguments
    • Returned from other functions
    • Stored in data structures
  • Closures: Go supports anonymous functions and closures, which can access variables from their enclosing scope even after that scope has finished executing.

  • Defer Statement: The defer keyword schedules a function call to be executed just before the function returns, useful for cleanup operations.


Closures and Pointers

Closures are anonymous functions that can reference variables from the function in which they are defined. Pointers in Go hold the memory address of a value, allowing you to share data across your program without copying it.

Closures

// Function Definition for Closure
func closureExample() func() int {
    x := 10
    return func() int {
        return x
    }
}
// Inside main function
closure := closureExample()
fmt.Println(closure()) // Outputs 10
Enter fullscreen mode Exit fullscreen mode

Pointers

// Function Definition for Pointers
func incrementValue(p *int) {
    *p += 1
}
// Inside main function
value := 5
incrementValue(&value)
fmt.Println(value) // Outputs 6
Enter fullscreen mode Exit fullscreen mode

Structs and Custom Types

Structs in Go are collections of fields, similar to classes in other languages but without inheritance. Go also allows you to create custom types based on existing types, with methods as well.

Custom type & method for it

type UserID int 
func (id UserID) Str() string {
   return fmt.Println(id)
}

var userId UserID = 42 
fmt.Println("User ID:", userID) 
fmt.Println("Formatted ID:", userID.String())
Enter fullscreen mode Exit fullscreen mode

Basic struct definition & Methods

type Product struct {
    ID        int
    Name      string
    Price     float64
    CreatedAt time.Time
}

func NewProduct(id int, name string, price float64) *Product {
    return &Product{
        ID:        id,
        Name:      name,
        Price:     price,
        CreatedAt: time.Now(),
    }
}

func (p Product) PriceWithTax(taxRate float64) float64 {
    return p.Price * (1 + taxRate)
}

func (p *Product) ApplyDiscount(percent float64) {
    p.Price = p.Price * (1 - percent/100)
}
Enter fullscreen mode Exit fullscreen mode

NewProduct is a constructor function for the Product struct. It initializes a new Product instance with the given values and sets the CreatedAt field to the current time using time.Now().

The Product struct also has two methods:

-** PriceWithTax :** This is a value receiver method, meaning it works on a copy of the Product instance. It_ does not modify the original Product_. It calculates and returns the price after applying a tax rate, but leaves the original Price unchanged.

  • ApplyDiscount : This is a pointer receiver method, meaning it works on the actual Product instance and can modify its fields. It applies a discount percentage directly to the Price field, reducing it in place.

Embedded struct

type InventoryItem struct {
    Product  // Embedded struct (anonymous field)
    Quantity int
    Location string
}
Enter fullscreen mode Exit fullscreen mode

It means that the InventoryItem struct embeds another struct, Product, without giving it a field name — this is called embedding an anonymous field in Go.

Creating a var of struct

laptop := Product{
    ID:        1,
    Name:      "Laptop",
    Price:     999.99,
    CreatedAt: time.Now(),
}
fmt.Println("Product:", laptop)
fmt.Println("Laptop name:", laptop.Name)
fmt.Println("Laptop price:", laptop.Price)
Enter fullscreen mode Exit fullscreen mode

Key Points

  • Value Semantics: Structs are value types—assign or pass them, and you get a copy.

  • Constructor Functions: Go doesn’t have built-in constructors, but you can make functions like NewType() to create structs.

  • Method Receivers: Methods use value receivers (copy) or pointer receivers (can change original).

  • Embedding: Go has no inheritance. Use embedding to include another struct directly—fields and methods are promoted.

  • Method Attachment: You can attach methods to any type, not just structs—as long as the type is defined in the same package.

Example:

type UserID int

func (id UserID) String() string {
    return fmt.Println(id)
}

var id UserID = 42
id.String() -> correct 
Enter fullscreen mode Exit fullscreen mode

Interfaces

Interfaces in Go define a set of method signatures. A type implements an interface by implementing its methods. Unlike many other languages, interface implementation is implicit in Go-there's no _"implements" _keyword.

Define an interface

type Greeter interface {
    Greet() string
}

Enter fullscreen mode Exit fullscreen mode

Independent types

// English type
type English struct{}

func (e English) Greet() string {
    return "Hello!"
}

// Spanish type
type Spanish struct{}

func (s Spanish) Greet() string {
    return "Hola!"
}
Enter fullscreen mode Exit fullscreen mode

Use of interfaces with types

// Function that accepts interface
func SayGreeting(g Greeter) {
    fmt.Println(g.Greet())
}

func main() {
    eng := English{}
    esp := Spanish{}

    SayGreeting(eng) // Output: Hello!
    SayGreeting(esp) // Output: ¡Hola!
}
Enter fullscreen mode Exit fullscreen mode

Key points

  • Interfaces in Go make code flexible and loosely connected.
  • Go types don’t need to declare they implement an interface — if they have the required methods, they do.
  • The empty interface interface{} (or any in Go 1.18+) has no methods, so all types implement it. Useful for unknown types.
  • Go encourages small, focused interfaces — often with just one or two methods.

Important points

var w io.Writer       // `w` is an interface variable of type Writer
w = os.Stdout         // Assign a concrete value of type *os.File to it
w.Write([]byte("Hello"))  // Call the Write method through the interface

Enter fullscreen mode Exit fullscreen mode
  • io.Writer is an interface that has a method Write([]byte) (int, error)
  • os.Stdout is a concrete value of type *os.File, which implements the io.Writer interface.
  • So w now holds the type *os.File and its value (os.Stdout).
  • You can call w.Write(...) even though you don’t know the exact type at compile time — that’s polymorphism.

Enums

Go doesn't have a dedicated enum type like some other languages, but it provides a pattern using constants and the iota identifier to create enumerated types.

type Month int

const (
    January Month = iota + 1 // Start with 1 instead of 0
    February
    March
    April
)

type Color string

const (
    Red    Color = "RED"
    Green  Color = "GREEN"
    Blue   Color = "BLUE"
)

//usage 
    var color Color = Blue

Enter fullscreen mode Exit fullscreen mode

iota is reset to 0 whenever the const keyword appears and increments by 1 for each constant in a block.


Generics

Generics in Go allow you to write functions and data structures that operate on values of any type, while still maintaining type safety. They're particularly useful for implementing reusable algorithms and data structures.

Generic function with a type parameter

func PrintItems[T any](items []T) {
    fmt.Println("Items contents:")
    for i, item := range items {
        fmt.Printf("[%d]: %v\n", i, item)
    }
}
Enter fullscreen mode Exit fullscreen mode

Using interface for constraints

type Numeric interface {
    int | int8 | int16 | int32 | int64 | float32 | float64
}

func Sum[T Numeric](values []T) T {
    var sum T
    for _, v := range values {
        sum += v
    }
    return sum
}
Enter fullscreen mode Exit fullscreen mode

Conclusion

Go is a powerful yet simple language that offers a strict type system, slices, pointers, generics, and interfaces with implicit inheritance.

In the next section, I'll discuss goroutines, wait times, and other related topics, covering the remaining features of Go.

Top comments (1)

Collapse
 
siddhant_bansal_8ee33e455 profile image
Siddhant Bansal

worth the read!

close