I recently decided to start learning Go. To get my hands dirty, I set up the local Go Tour (~/go/bin/tour, as per Go Tour #3) and began working my way through the basics. I've just wrapped up the section on Flow control statements: for, if, else, switch, and defer.
Getting to this point has been an interesting experience, filled with moments of confusion, a few "wow!"s here and there, and some syntax that felt entirely alien at first. Here is a look at my progress so far and the concepts that stood out to me.
1. Some Rather Unusual Syntax
Coming from other languages, the way Go handles functions and returns took some getting used to. Take a look at this multiple-return example from Go Tour:
package main
import "fmt"
func swap(x, y string) (string, string) {
return y, x
}
func main() {
a, b := swap("hello", "world")
fmt.Println(a, b)
}
Having only one type declaration for multiple variables, followed by multiple return types in parentheses, felt very weird initially.
But things got even weirder when I hit naked returns:
package main
import "fmt"
func split(sum int) (x, y int) {
x = sum * 4 / 9
y = sum - x
return
}
func main() {
fmt.Println(split(17))
}

In Go, you can name your return values at the top of the function. When you do that, a simple return statement without arguments automatically returns whatever those variables currently hold. I don't think I've seen this pattern in other languages before. While it's neat for short functions, I can see how it might make larger functions a bit harder to read if you aren't careful.
2. The Constant Conundrum (and an "Aha!" Moment)
I ran into a bit of a roadblock when trying to understand this example from the tour:
package main
import "fmt"
const (
// Create a huge number by shifting a 1 bit left 100 places.
// In other words, the binary number that is 1 followed by 100 zeroes.
Big = 1 << 100
// Shift it right again 99 places, so we end up with 1<<1, or 2.
Small = Big >> 99
)
func needInt(x int) int { return x*10 + 1 }
func needFloat(x float64) float64 {
return x * 0.1
}
func main() {
fmt.Println(needInt(Small))
fmt.Println(needFloat(Small))
fmt.Println(needFloat(Big))
}
Initially, I got an error when I tried running fmt.Println(needInt(Big)).
I was under the impression that Go just infers types cleanly, so I was confused about why needFloat(Big) worked while needInt(Big) threw a compilation error.
After digging into it, I learned something crucial: untyped constants in Go can be arbitrarily large. They only take on a specific type size when they are used in a context that requires one.
- On my system, a standard
intis 64 bits wide, meaning it cannot hold a number as massive as1 << 100. float64works because it represents values using floating-point scientific notation, allowing it to scale up to represent much larger numbers (with potential limits on precision tho).
Once that clicked, the error made complete sense.

3. Rethinking the Loop
Go keeps things simple by only having one looping construct: for. However, how you use it can vary wildly.
I was quite surprised to find out that the initialization and post statements in a for loop are completely optional:
package main
import "fmt"
func main() {
sum := 1
for ; sum < 1000; {
sum += sum
}
fmt.Println(sum)
}
Looking at those empty semicolons felt a bit strange. But then it was explained that you can drop the semicolons entirely:
package main
import "fmt"
func main() {
sum := 1
for sum < 1000 {
sum += sum
}
fmt.Println(sum)
}
Since I have some experience with Python, this felt much more familiar. It turns out Go doesn't have a while keyword because a for loop with only a condition is Go's version of a while loop.
And if you want an infinite loop? You just drop the condition entirely:
package main
func main() {
for {
}
}
I dunno why, but it's kinda funny how simple that "forever" loop looks.

4. Conditionals: If Python and JS Had a Baby
When writing if statements in Go, I couldn't help but feel like the syntax is a hybrid of Python and JavaScript:
package main
import (
"fmt"
"math"
)
func sqrt(x float64) string {
if x < 0 {
return sqrt(-x) + "i"
}
return fmt.Sprint(math.Sqrt(x))
}
func main() {
fmt.Println(sqrt(2), sqrt(-4))
}
You don't need parentheses around the condition (like Python), but you absolutely must use curly braces (like JavaScript). It felt slightly unusual at first, but it quickly became comfortable to write.
Another feature that really caught my attention was the ability to execute a short statement before the conditional check:
package main
import (
"fmt"
"math"
)
func pow(x, n, lim float64) float64 {
if v := math.Pow(x, n); v < lim {
return v
}
return lim
}
func main() {
fmt.Println(
pow(3, 2, 10),
pow(3, 3, 20),
)
}
Being able to declare a helper variable v right inside the if scope, where it is only available within the conditional block, is so clean, like, wow. It keeps the outer function scope clutter-free.
What's Next?
I am slowly adapting to Go's design philosophy. While some of the syntax choices felt bizarre at first, I am starting to appreciate how the language forces you to write explicit, readable code.
My next step in the tour is to dive deeper into structs, slices, and maps (even pointers) to see how they... to be honest, I dunno what they do. But for now, I need to take a break. HMU tho ๐
