
Learning Go from scratch can be a daunting task, but with the right approach, you can become a Golang Ninja in no time. The Go programming language is designed to be easy to learn and fun to use, making it a great choice for beginners.
Go is statically typed, which means that the compiler checks the types of variables at compile time, preventing type-related errors at runtime. This makes it a great choice for systems programming and other applications where reliability is crucial.
To get started with Go, you'll want to install the Go toolchain on your computer. This includes the Go compiler, the Go command-line tool, and other tools you'll need to write and run Go programs. The Go toolchain is available for Windows, macOS, and Linux.
The Go language has a simple syntax and is designed to be easy to read and write. It's a great choice for beginners who want to learn a programming language quickly and efficiently.
Discover more: Is Golang a Functional Language
Getting Started
To get started with Go, you'll want to familiarize yourself with the essential commands that will help you throughout your development journey. These commands are go build, go run, go fmt, go install & go get, and go test.
Go build is a command that compiles your code and creates an executable file. You can use environment variables to build for different platforms if needed.
You can use go run to build and immediately execute your code in one step, saving you time and effort. This command is a convenient way to get started with testing your code.
Here's a quick rundown of the essential commands to get you started with Go:
Started with
Getting started with Go can be a bit overwhelming, but don't worry, I've got you covered. You can start by understanding the essential Go commands that will help you throughout your development journey.
go build is a command that compiles your code and creates an executable file. This is useful for building your code for different platforms, and you can use environment variables to make it even easier.
Expand your knowledge: Go High Level Twilio Integration

go run is a convenient command that builds and immediately executes your code in one step. This is a great way to quickly test your code and see if it works as expected.
To manage dependencies and install external packages, you can use go install and go get. These commands are essential for working with Go's package management system.
go test is a command that runs test files to ensure your code works as expected. This is a crucial step in the development process to catch any bugs or errors in your code.
Here's a summary of the essential Go commands you should know:
Random Numbers
Getting started with random numbers in Go is a great place to begin. The math/rand package provides functions for generating random numbers, which is essential for many applications.
By default, the rand package produces pseudorandom numbers, meaning they're not truly random but appear to be so. This is because they use a fixed seed value of 1.
For true randomness, you need to provide a changing seed value, which can be done using the current time. This ensures your random numbers are unique and unpredictable.
The math/rand package makes it easy to get started with random numbers in Go.
Recommended read: Golang Reflect to Call Function in Package
Packages and Variables
Packages in Go are a fundamental concept, and understanding them is key to writing effective code. In Go, a package represents a project or workspace, and all files belonging to the same project must belong to the same package.
You have two types of packages in Go: Executable Packages and Reusable Packages. Executable Packages must begin with the statement "package main" and include a main() function, and they produce an executable file when built. Reusable Packages, on the other hand, don't produce executable files but can be imported into other projects.
To use the functionality of packages, you need to explicitly import them, as the main package doesn't have access to the standard library by default. This is where things get interesting - you can import multiple packages, but you need to declare them at the top of your file.
Here are the two types of packages in Go, summarized in a table:
Understanding Packages
In Go, a package represents a project or workspace. All files belonging to the same project must belong to the same package. Go offers two types of packages: Executable Packages and Reusable Packages.
Executable Packages must begin with the statement package main and include a main() function. These packages produce an executable file when built.
Reusable Packages don't use the name "main" and are considered reusable, similar to a library or dependency. These packages don't produce executable files but can be imported into other projects.
To use the functionality of packages, you need to explicitly import them. The main package doesn't have access to the standard library by default.
Here are the two types of packages in Go:
Variables in
Variables in Go are a crucial part of the language, and understanding how they work is essential for any programmer.
Go is a statically typed language, meaning once a variable's type is declared, it cannot be changed. This makes it easier to catch type-related errors early on.
Take a look at this: Golang Type
The shorthand initialization (:=) is more common in Go, as the language can infer the variable type from the assigned value. This makes coding more efficient and less error-prone.
Common variable types in Go include bool, int, string, and float64. These types are used for boolean values, integer values, text values, and decimal numbers, respectively.
Here's a quick rundown of the common variable types in Go:
Capitalization and Visibility
In Go, capitalization plays a crucial role in determining visibility of names. Capitalized names, such as types, functions, and variables, are exported and accessible from other packages.
This convention is simple yet effective, eliminating the need for explicit visibility modifiers like public or private. In fact, it's a game-changer for developers, making code organization and reuse much easier.
To understand this concept better, let's look at the difference between capitalized and lowercase names:
- Capitalized names are exported and accessible from other packages.
- Lowercase names are only accessible within the package where they are defined.
This distinction is essential to keep in mind when working with packages and variables in Go. By following this convention, you can write more organized and maintainable code.
Leveraging Built-in Functions

Built-in functions are a powerful tool in programming, and they can save you a lot of time and effort. They're like pre-made recipes that you can use to accomplish a task without having to write the code from scratch.
You can use built-in functions to perform a wide range of tasks, such as converting data types, checking the length of a string, or even generating random numbers. For example, the `len()` function can be used to count the number of characters in a string, like in the example where we used `len("hello")` to get the result of 5.
Built-in functions are often faster and more efficient than writing your own code, which can make your program run more smoothly. In the example where we used `len()` to count the characters in a string, it took only a fraction of a second to produce the result.
You can also use built-in functions to perform mathematical operations, such as calculating the square root of a number or rounding a value to the nearest integer. For instance, the `pow()` function can be used to calculate the power of a number, like in the example where we used `pow(2, 3)` to get the result of 8.
Built-in functions can also be used to work with dates and times, such as calculating the difference between two dates or formatting a date in a specific way.
Curious to learn more? Check out: Golang Generic Function
Input and Output
Go's I/O system is built around two main interfaces: Reader and Writer. These interfaces make it easy to process data from various sources (files, network, etc.) and write to different destinations.
The Reader interface is implemented by sources of data, providing a Read([]byte) (int, error) method that populates a byte slice with data. This allows you to easily read data from different sources, such as files or network connections.
The Writer interface is implemented by destinations for data, providing a Write([]byte) (int, error) method that outputs data from a byte slice. You can create custom writers to suit your specific needs.
Here are the two main interfaces in Go's I/O system:
Working with HTTP and IO
Working with HTTP and IO is a breeze in Go. Go's I/O system is built around two main interfaces: Reader and Writer.
You can use the Reader interface to source data from various places, like files or the network. The Reader interface provides a Read([]byte) (int, error) method that populates a byte slice with data.
Take a look at this: Check Type of Interface Golang
The Writer interface is perfect for sending data to different destinations, such as files or network connections. The Writer interface provides a Write([]byte) (int, error) method that outputs data from a byte slice.
Go makes it easy to process data from various sources and write to different destinations. You can even create custom writers to suit your needs.
Here are the two main I/O interfaces in Go:
Format Specifiers
Format Specifiers are a crucial part of Go's Input and Output system.
The %v format specifier is the default format, which is useful for quickly printing values. For example, fmt.Printf("%v", 123) will print 123.
You can also use %v to print structs, but it will only show the values, not the field names. To include the field names, you need to use %+v instead.
The %T format specifier is useful for printing the type of a value, which can be helpful for debugging. For instance, fmt.Printf("%T", 123) will print int.
The %d format specifier is used for printing integers, and it's very similar to %v. However, it's worth noting that %d is more commonly used for printing integers.
The %f format specifier is used for printing floating-point numbers, and you can use the precision specifier to control the number of decimal places. For example, fmt.Printf("%.2f", 123.456) will print 123.46.
The %s format specifier is used for printing strings, which is exactly what it sounds like. For instance, fmt.Printf("%s", "hello") will print hello.
The %t format specifier is used for printing booleans, which will print true or false. For example, fmt.Printf("%t", true) will print true.
The %p format specifier is used for printing pointers, which can be a bit tricky to understand. However, it's useful for debugging and can help you identify memory locations. For instance, fmt.Printf("%p", &name) will print 0xc000010200.
The %c format specifier is used for printing characters, which is useful for printing individual characters. For example, fmt.Printf("%c", 65) will print A.
Consider reading: C Golang
Data Structures
In Go, slices are the preferred collection type due to their flexibility and ability to grow or shrink in size. They're also passed by reference, which is a big advantage.
Arrays, on the other hand, have a fixed size and are passed by value, making slices a more efficient choice for most use cases. This is why you should favour slices over arrays most of the time, unless there's a specific reason to use arrays.
You can create slices from existing slices, which is a useful feature to keep in mind when working with data. This can be done by simply assigning one slice to another.
String Manipulation
String manipulation is a crucial aspect of working with data structures, and Go provides some amazing functions to make it easy.
The most commonly used function for string manipulation in Go is the sprintf-style formatting, which allows you to control the width and precision of your output.
You can use the %v placeholder to format your output, and the %s placeholder to format strings.
Go also provides a function called fmt.Sprintf, which is similar to the sprintf function in other programming languages.
To control the width and precision of your output, you can use the %f placeholder, followed by a width and precision specification, like this: %10.2f.
This will format the number to have a width of 10 characters and a precision of 2 decimal places.
Explore further: Golang Sprintf
Arrays and Slices
Go provides two primary data structures for working with collections: arrays and slices. Arrays are a fundamental part of Go programming.
Arrays are a fixed-size collection of elements, but they're not the preferred choice in most Go programs.
Slices, on the other hand, are more flexible and can grow or shrink in size. They're passed by reference, making them a great option for dynamic data.
You can create slices from existing slices, which is a handy feature.
Maps
Maps are a fundamental data structure in Go, and they're similar to dictionaries or hash tables in other languages. They allow you to store and retrieve data using a key-value pair.
In Go, maps require all keys to be of the same type and all values to be of the same type. This is in contrast to structs, which can have different value types.
A very interesting type is a map of string to interface, where the value can be any type. This is useful for storing any type of data in a map.
In Go 1.18, the interface{} type was replaced by any, which is more readable. In Go 1.18+, any is preferred as the modern syntax.
To decide when to use maps vs. structs, consider the following:
- Use maps when all keys and values are of the same type, and when you need to add or remove entries dynamically.
- Use structs when you need different value types and when you know the exact fields you need at compile time.
- If you’re coming from JS/TS, use a basic rule of thumb: use map where you would’ve used plain objects in JS and use structs where you would’ve used strict type with interfaces in TS.
Structs in
Structs in Go are composite data types that group together variables of different types under a single name. This allows for a more organized and efficient way to store and manipulate data.
You can embed structs within other structs, which enables a hierarchical structure for your data. This is particularly useful when you need to store related information together.
Structs can have different value types, which is in contrast to maps where all keys and values are of the same type. This flexibility is one of the key advantages of using structs.
Here's a quick reference to help you decide between using maps and structs:
If you're coming from JavaScript or TypeScript, a good rule of thumb is to use a map where you would've used a plain object, and use a struct where you would've used a strict type with interfaces.
Type Conversion
Type conversion in Go is straightforward. You use the target type as a function and pass the value to be converted.
Working with files often requires converting between strings and byte slices. This can be done using the target type as a function, such as `string()` or `[]byte()`.
You can convert a string to a byte slice by passing it to the `[]byte()` function. For example: `[]byte("Hello, World!")`.
Identify Runtime Slice by String

Runtime functions like runtime.slicebytetostring are often obfuscated, making them difficult to identify. Function names are obfuscated within the Garbled binary.
To identify runtime.slicebytetostring, we use fingerprinting techniques within the disassembly throughout the Binary Ninja database. This involves enumerating all basic blocks within all functions until the candidate pattern is discovered.
A series of fingerprinting techniques are used to find candidate locations of obfuscation sequences. Candidate locations are found by enumerating all basic blocks within all functions.
The outgoing edges of the candidate basic block are recursively enumerated until a call sequence pattern to runtime.slicebytetostring is identified. This helps us pinpoint the exact location of the function.
Once the runtime.slicebytetostring location has been identified, the cross-references to all callsites of runtime.slicebytetostring are acquired. This provides a list of all functions that call runtime.slicebytetostring.
The incoming edges are recursively enumerated until the beginning of all obfuscation sequences are found. This helps us understand the flow of data and control within the program.
Curious to learn more? Check out: Golang Runtime
Concurrency and Channels
Concurrency in Go is elegant, thanks to goroutines and channels. Goroutines are lightweight threads that can run concurrently, making it easy to write concurrent programs.
Channels provide a way for goroutines to communicate with each other, but they can only share data of a specific type. You can create different channels to share different types of data.
Receiving a message from a channel is a blocking call, just like a network call. This means that if the main goroutine faces a code like <-channel, it pauses execution and waits for the value from the channel.
You can create an infinite loop that respawns goroutines for continuous monitoring. This is a useful technique for keeping your program running smoothly.
Remember to never directly access variables from outside a goroutine without proper synchronization. Always pass such values as function arguments to avoid race conditions.
Concurrency is about structuring a program to handle multiple tasks that could start, run, and complete in overlapping time periods. Parallelism, on the other hand, is about executing multiple tasks simultaneously, literally at the same time.
Explore further: Golang Time since
The Go scheduler manages concurrency and parallelism by distributing goroutines across available CPU cores. This setting determines how many OS threads the Go scheduler will use to run your goroutines.
Here's a summary of the key differences between concurrency and parallelism:
- Concurrency: structuring a program to handle multiple tasks that could start, run, and complete in overlapping time periods.
- Parallelism: executing multiple tasks simultaneously, literally at the same time.
Function literals, or anonymous functions, are useful for delaying execution or managing goroutine lifetimes. They can be used to create small, one-time-use functions that don't clutter your code.
Variable capture can be tricky when working with goroutines. Be cautious when accessing variables from an outer scope inside a goroutine, as the variable might change before the goroutine runs.
Channel synchronization is a powerful technique for managing concurrency. Use channels not just for data transfer, but also for synchronization between goroutines. This can help prevent race conditions and make your code more robust.
Consider reading: Golang Set Env Variable
Error Handling and Testing
Error handling is a crucial aspect of Go programming. In Go, functions return error values instead of throwing exceptions, making error handling explicit and encouraging developers to consider error cases.
Broaden your view: Golang Create Error
When you can't handle an error, it's best to propagate it to the caller. This is a common error handling pattern in Go. Propagating the error ensures that the error is not lost and can be handled by the caller.
To test error handling in Go, you can use a combination of the three error handling patterns: propagate, handle, and log. Here are some common error handling patterns in Go:
By understanding these error handling patterns, you can write more robust and reliable code in Go.
Error Handling
Error handling is an essential aspect of programming, and Go takes a unique approach to it. Instead of using exceptions, functions in Go return error values.
This explicit approach encourages developers to consider error cases and handle them properly. Propagating the error, handling it, logging and continuing, and logging and exiting are common error handling patterns.
Propagating the error involves returning the error to the caller when it can't be handled. This is a straightforward approach that ensures the error is not ignored.
Handling the error requires taking appropriate action based on the error. This might involve retrying the operation, providing a default value, or displaying an error message to the user.
Logging and continuing is another approach, where the error is logged but execution continues if possible. This is often used for non-fatal errors that don't require the program to exit.
Logging and exiting is used for fatal errors, where the program logs the details and exits. This approach is typically used for errors that cannot be recovered from.
Here are the common error handling patterns in Go:
- Propagate the error
- Handle the error
- Log and continue
- Log and exit
Testing In
Testing in Go is a breeze, thanks to its integrated toolchain. Test files must end with _test.go and contain functions that start with Test.
You can run tests using the go test command, but don't forget to clean up before and after the test. This ensures your tests are reliable and accurate.
Advanced Topics
As a Go developer, you've likely heard of goroutines and channels, but have you ever wondered how they can be used to handle concurrency in a more advanced way?
The select statement is a powerful tool for handling multiple channels in a goroutine. It allows you to specify a default case to handle any remaining values.
In Go, the runtime's GOMAXPROCS function can be used to limit the number of goroutines that can run concurrently. This can be useful for preventing a program from consuming too much CPU or memory.
Embedding and Composition
Go's embedding feature allows you to reuse code without the complexity of inheritance hierarchies.
Embedding promotes fields and methods from one struct to another, making them accessible directly from the new struct instance. This is in contrast to inheritance, where a new struct would "inherit" from the original struct.
Field promotion is the process of moving fields from one struct to another, in this case, from Animal to Dog. This means you can access fields like Name and Age directly from a Dog instance.
Method promotion works similarly to field promotion, allowing you to call methods defined on the Animal struct directly on a Dog instance.
For your interest: Golang Extend Struct
Here's a breakdown of what happens during embedding:
- Field Promotion: Fields are moved from the original struct to the new struct.
- Method Promotion: Methods are moved from the original struct to the new struct.
- No Inheritance: The new struct doesn't inherit from the original struct; it simply contains it.
If the new struct has fields that conflict with the embedded fields, the new struct's fields take precedence. This can be a gotcha when working with embedded structs.
To avoid this, you can override the method to use the new struct's fields instead of the embedded fields.
On a similar theme: Golang Generic Struct
String Obfuscation
String obfuscation is a common technique used to hide sensitive information in code. It's particularly challenging to deal with due to the pseudo-random choice of deobfuscation techniques at compile time.
One approach to resolving string literal obfuscation is to extract and emulate the given sequence to recover the original string literal. This can be done by using the IDA Hex-Rays API, but it's not always viable due to the complexity of the obfuscation.
The Golang abstract syntax tree (AST) is matched against sequences targeted by Garble for obfuscation, and the original literal is obfuscated with a chosen technique. The resulting code is then compiled, resulting in a binary containing solely obfuscated literals and their deobfuscation routines.
Expand your knowledge: Golang String Literal
This can result in large obfuscation sequences that are difficult to reimplement for all given scenarios. In such cases, it's better to pursue the capture and emulation of relevant sequences to recover each string literal.
To identify the runtime.slicebytetostring function, fingerprinting techniques are used to find candidate locations of obfuscation sequences in the disassembly. This involves enumerating all basic blocks within all functions until the candidate pattern is discovered.
Here are some common obfuscation techniques used to hide string literals:
- Garble
- IDA Hex-Rays API
- Abstract syntax tree (AST) matching
These techniques are chosen pseudo-randomly at compile time, making it difficult to reimplement all deobfuscation scenarios.
Best Practices
Function literals, also known as anonymous functions, are useful for delaying execution or managing goroutine lifetimes.
Be cautious when accessing variables from an outer scope inside a goroutine, as the variable might change before the goroutine runs.
Use channels not just for data transfer but also for synchronization between goroutines.
The use of interfaces is an easy-to-understand technique that enables the creation of both testable and extensible code.
Use interfaces and dependency injection to future-proof your designs.
Function literals can be used to delay execution, which is useful in certain situations, but be careful not to overuse them and make your code harder to read.
By using channels for synchronization, you can write more concurrent and efficient code that's easier to understand and maintain.
Working with JSON and Background
Go has excellent support for JSON encoding and decoding, making it a great language for building web services.
The struct tags, like json:"first_name", control how fields are named in the JSON output, allowing for flexibility in how your data is represented.
This makes it easy to work with JSON data in Go, and is a key feature for building robust and efficient web applications.
Discover more: Golang Json Unmarshal
Working with JSON
Working with JSON is a breeze in Go, thanks to its excellent support for JSON encoding and decoding.
The struct tags, like json:"first_name", control how fields are named in the JSON output. This is a powerful tool for customizing your JSON output to fit your needs.
Go's JSON support is robust and reliable, making it a great choice for working with JSON data.
For another approach, see: Golang Json Tags
Background
I primarily work on Windows for development purposes. I've been looking for an alternative to Make, a build tool primarily targeted at Unix and Unix-like operating systems.
Make is used in many popular Go projects on Github to automate tasks like linting, build, testing, and deployment. I've found that setting up Make requires environments like mingw, which can be a hassle.
I came across a post by Julia Evans on Ninja, a build automation tool that's lightweight, fast, and easy to configure. It's also cross-platform, making it easy to set up across Windows and Linux.
Ninja is not meant to be used by writing build files by hand, but rather with a "build generator" to generate Ninja build files. This is in contrast to Make, which has features like string manipulation that are not available in Ninja.
Expand your knowledge: Golang Build
Pointers and Value Types
Go is a pass-by-value language, which means function arguments receive copies of the original values.
This can be a bit tricky, especially if you're used to languages that pass arguments by reference. To illustrate this, let's say you have a variable x and you pass it to a function. If you modify x directly within the function, it won't be reflected in the original variable.
Understanding the difference between value types and reference types is crucial. Here's a quick rundown of what you need to know:
- Value types: int, float, string, bool, structs
- Reference types: slices, maps, channels, pointers, functions
With reference types like slices and maps, you don't need to worry about pointers to modify their contents because they already contain internal pointers to their data. This means you can modify them directly without creating a new copy.
Interfaces
In Go, interfaces are a powerful tool that enable polymorphism, allowing for more flexible and reusable code.
A type automatically satisfies an interface if it implements all the methods declared in the interface, eliminating the need for explicit declaration like "implements" in other languages.
Interfaces can be combined, requiring a type to implement all methods from both the Reader and Closer interfaces to satisfy the ReadCloser interface.
This means you can create more complex interfaces by combining simpler ones, making your code even more flexible and reusable.
Additional reading: Golang Interface Type
Featured Images: pexels.com


