From Zero to Go Hero: Learn Go Programming with Me - Part 2
Understanding Core Concepts

Search for a command to run...
Understanding Core Concepts

No comments yet. Be the first to comment.
Lessons from Code, Content, and Consistency

Reliability basics: Outbox pattern + why it matters

Introducing Kafka/Redpanda + move to event-driven workflow

Connect Orders ↔ Inventory (first working service-to-service flow)

Inventory service + gRPC + proto contracts

Hey there! Welcome to Part 2 of the series of Learning Go with me. In Part 1 we set the stage by introducing the project, defining its goals, and setting up the basic folder structure for our Go project.
Now it’s time to dive deeper into some fundamental concepts of Go programming.
In this part, we’ll cover core topics like how Go organizes code using packages and imports, how exported names work, and explore basic Go constructs such as types, variables, constants, functions, slices, maps, loops, and conditionals. Then we’ll also understand how structs and pointers work.
I will try my best to explain each concept in a beginner-friendly way and in a way that I understand because I am also learning with you. I will try to incorporate these concepts in a way that after understanding it we can directly apply it to our CRUD API project.
By the end of this section, you’ll have a strong grasp of these concepts and be ready to use them to bring our API to life.
Let’s jump right in it without wasting any more time
In Go, packages are a way to organize and reuse code. What does that mean?
Think of a Package like a Folder on your computer. Inside that folder, you have related files. Similarly in Go, the files inside a package folder work together to provide some functionality
For example:
math package in any other language then you must know that it contains tons of mathematical operations. So all these operations are inside this math packageAnd when you want these functions which are defined somewhere else inside your program, you use “import“.
Standard Library Packages:
math for mathematical operations, net/http for handling HTTP requests, encoding/json for working with JSON, etc.Third-Party Packages:
go get the command. Those belong to this type.Custom Packages:
main package is special because it’s where the program starts running. When we define the main package inside a file, Go will know from where it should start the program.
// cmd/main.go
package main
// config/config.go
package config
// databse/db.go
package database
// internal/books/handler.go, model.go and service.go
package books
// routes/routes.go
package routes
//pkg/logger
package logger
import a keyword.import "fmt"
import (
"fmt" // for printing.
"net/http" // to handle HTTP requests and responses.
"encoding/json" // to parse and generate JSON data
)
So in Go, we don’t have keywords like private or public like other languages.
If the name starts with an uppercase letter, then it is considered as exported, meaning it can be accessed from outside the package where they’re defined.
Names starting with a lowercase letter are unexpected and private to the package.
type Book struct { // Exported struct
Title string // Exported field
Author string // Exported field
}
type car struct { // Unexported struct
brand string // Unexported field
isElectric bool // Unexported field
}
Above we created two structs (don’t worry if you don’t know about structs. We will see it in detail in this blog below).
Here Book starts with an uppercase letter, meaning we can get this struct in other packages whereas car starts with a lowercase letter, meaning we can’t access this struct anywhere else.
Go is a statically typed language. This means every variable must have a type that’s known at compile time. Common types include:
int: Integer values
string: Text data
bool: Boolean values (true/false)
float: Decimal values
var age int = 24
var name string = "Dhruv"
age := 24
name := "Dhruv"
var age int // Defaut: 0
var name string // Default: ""
Constants are immutable values, which means we can’t change them once defined.
Use const keyword to declare constants
Why Use Constants?
We use it to prevent accidental changes to critical values.
To improve the readability and maintainability of code
const apiVersion = "v1"
const defaultPort = 8080
const (
apiVersion = "v1"
defaultPort = 8080
)
func keyword to declare functions in Go.func functionName(){
// logic
}
func functionName(parameter1 type, parameter2 type){
// logic
}
// Single value return
func functionName() int {
return 0
}
// For multiple value return
func functionName() int, string {
return 0, "Hello"
}
if statement is used for decision-makingmarks := 85
if marks >= 9- {
fmt.Println("Grade: A")
} else if marks >= 75 {
fmt.Println("Grade: B")
} else {
fmt.Println("Grade: F")
}
if. In this, we are first declaring num , and right after that we are checking the condition for it.if num := 10; num%2 == 0 {
fmt.Println("The number is even.")
} else {
fmt.Println("The number is odd.")
}
for loop for looping. There are different ways you can use for loop. Let’s see it in action// Basic `for` loop
for i:= 0; i<5; i++ {
fmt.Println("Value of i: ,", i)
}
// Infinite Loop
count := 0
for {
fmt.Println("Looping forever")
count++
if count == 3 {
break // Exists the loop
}
}
// Condition Only Loop
count:= 0
for count < 3 {
fmt.Println("Count: ", count)
count++
}
If you come from programming languages like Javascript, Dart, or Python, you must be familiar with class and objects.
In Go we don’t have that. We use struct to group data together.
Let’s take an example of our CRUD API project. We need to represent a book with multiple details like a title, id, name, etc.
So instead of creating separate variables for each piece of data, you can use a struct to combine all of those into one entity.
type keyword followed by the structure name and its field. Each field has a name and a typetype Book struct {
ID string
Title string
Author string
Year int
}
var book1 Book // Creates an instance of `Book` struct
book1.ID = "1" // Assigns the value "1" to the ID field of struct
book1.Title = "Maths"
book1.Author = "Jane"
book1.Year = 2012
Using Field Names
book1 := Book{
ID: "1"
Title: "Maths"
Author: "Jane"
Year: 2012
}
Without Field Names
book1 := Book{
"1"
"Maths"
"Jane"
2012
}
A method is a function that is attached to a specific type. This type of method initialization was new and kinda weird for me at first. Because usually we are required to create methods inside a class in other languages right? but here we create it separately and then attach it to the structure using the receiver.
Let me show you how
type Book struct {
ID string
Title string
Author string
Year int
}
func (b Book) SayBookName() {
fmt.Println("This book name is: ", b.Title)
}
func main() {
book1 := Book{
ID: "1"
Title: "Maths"
Author: "Jane"
Year: 2012
}
book1.SayBookName() // Call the SayBookName method
}
As you can see to attach this SayBookName method to Book struct we provided receiver before the method name inside the parenthesis (b Book). So Inside this file wherever any method has this receiver it will automatically get attached to this struct.
You can also nest struct inside a struct like this
type Book struct {
ID string
Title string
Author Author // Nested struct
Year int
}
type Author struct {
FirstName string
LastName string
}
func main() {
book1 := Book{
ID: "1"
Title: "Maths"
Author: Author{
FirstName: "Jane"
LastName: "Doe"
}
Year: 2012
}
}
A slice in Go is a data structure used to hold a collection of elements of the same type.
Think of it as a more powerful and flexible version of an array.
While arrays have fixed sizes, slices can grow and shrink dynamically.
It’s really simple to use
Using the make function
make function creates a slice with a specific length and capacity.slice := make([]int, 3, 5) // Create a slice of type int, of length 3 and capacity 5
Here length represents the number of elements currently in the slice which is 3 here: [0,0,0]
And Capacity represents the total space allocated for the slice which is (5). This means this slice can have max 5 elements in it.
Using a Slice Literal
slice := []int{1,2,3,4,5}
append function.slice := []int{1,2,3}
slice = append(slice, 4,5) // adding 4 and 5 to the slice
for loop to access elements in a sliceslice := []int[1,2,3,4,5]
for index, value := range slice {
fmt.Println("Index: ", index, "Value: ",value)
}
for _, value := range slice {
fmt.Println("Value: ",value)
}
Here range the keyword is used to iterate over elements in a variety of data structures. In the above example, we use range to print the index and value in a slice.
We use underscore (_) to skip the variable if it’s not needed
slice := []int{1,2,3,4,5}
newSlice := slice[1:3] // Slice from index 1 to 2 (3 is exclusive)
// newSlice: [2,3]
len() and cap() functionsslice := []int{1,2,3,4,5}
fmt.Println("Length: ", len(slice)) // 5
fmt.Println("Capacity: ", cap(slice)) // 5
We can add a whole new slice to an existing slice using . . . (unpack operator)
slice := []int{1,2,3,4,5}
slice2 := []int {6,7,8}
combined := append(slice, slice2...) // [1,2,3,4,5,6,7,8]
This is another very helpful data structure in Go which is built in inside it. It stores key , value . This is similar to dictionaries in Python or hash maps in other programming languages.
Keys must be of a comparable type that is string, int and values can be of any type.
This data structure is useful, especially for lookups when the key uniquely identifies the data. In our case, it is Book ID
makemyMap := make(map[string]int)
myMap["Maths"] = 2
myMap["Biology"] = 3
key and int represents valueUsing Map Literal
myMap := map[string]int{
"Math": 2,
"Biology": 3,
}
myMap := map[string]int{
"Math": 2,
"Biology": 3,
}
fmt.Println(myMap["Maths"]) // 2
myMap := map[string]int{
"Math": 2,
"Biology": 3,
}
value, ok := myMap["Physics"]
if exists {
fmt.Println("Value: ", value)
} else {
fmt.Println("Key does not exist")
}
value, exists := myMap["Physics"]: ok is true if the key is present and false otherwise.myMap := map[string]int{
"Math": 2,
"Biology": 3,
}
myMap["Physics"] = 4 // Adding new element
myMap["Math"] = 1 // Updating exisitng element
delete() function to remove a key-value pair from the mapmyMap := map[string]int{
"Math": 2,
"Biology": 3,
}
delete(myMap, "Math") // Remove the key "Math"
for loop with range to iterate over all key-value pairs.myMap := map[string]int{
"Math": 2,
"Biology": 3,
}
for key, value:= range myMap {
fmt.Println("Key: ", key, "Value: ", value)
}
I think that’s pretty much all you need to know for now. We learned lots of fundamental stuff in this article. I tried to make it as concise as possible in order to quickly grasp the underlying concepts because going into too much detail with all the theory doesn’t make sense at this point.
There is still one concept remaining which is Pointers. I wanted to cover it in this blog only but then thought it would become quite a big read so I will be explaining it in detail in the next blog.
I hope after this article you are at least comfortable with the basic stuff and syntaxes. Just practice the syntax that you find difficult to grasp. It will all make sense and your hands will then flow naturally on the keyboard afterwards.
See you in the next one, until then…
