In the last article Transition from the Java developer to a Software developer, we discussed about our learnings and the change in thought process to require a build application using Golang. Go is a simple and powerful language, but unlike any other language, it also has few Gotchas… Some of these mistakes are natural traps for programmers, especially if you are accustomed to some other language thought process.
Gotcha is a valid construct in a system, program or programming language that works as documented but is counter-intuitive and almost invites mistakes because it is both easy to invoke and unexpected or unreasonable in its outcome.
Source: WIKIPEDIA
In this article I am trying to list couple of Gotchas along with alternatives to address them, I came across during last 3 years learning in the Golang. I would love to hear feedback and learn from you in case I have missed some of them and you have encountered these while development… I have divided these Gotchas in to multiple categories based on the complexity.

Simplest

These are very simplest form of Gotchas as we can catch them at compile time. Compiler or linters (static analysis tools) complains about these mistakes and we can resolve them quickly. Some of them are:
unexpected semicolon or newline before {
Unlike any other language we are not allowed to place opening brace in the separate line. You can thank automatic semicolon injection.
*** declared and not used
Go does not allow you to have to unused variables in inside function, but is ok to have unused global variables.
imported and not used
Go does not allow to have unused imports, if you really want to import one for initialization of drivers or something else, you can make use of _ as its package name to avoid compilation failure.
non-declaration statement outside function body
Go does not allow us to declare a variable using short variable declarations outside functions.
no new variables on left side of :=
Unlike any other language, Go does not allow us to redeclare a variable in the standalone statement, but it is allowed in the multi-variable declarations where at least one new variable is present.
non-name *** on left side of :=
As per Rob Pike in Issue #6842 The := notation is a shorthand for common cases. It's not meant to cover every possible declaration one may write.
use of untyped nil
If you don't specify the variable type the compiler will fail to compile your code because it can't identify the type of variable to define its zero-value as nil.
invalid argument *** (type map[string]int) for cap
Map capacity can be specified at the time of creation using make(map[string]int, 50), but can't use cap function.
cannot use nil as type string in assignment
Unlike Java we can’t create a nil string in the Go. Zero-value for strings is blank "".
invalid operation: s == nil (mismatched types string and nil)
As we can’t create nil string in Go, compilation will fail if we try to compare a string value with the nil
cannot assign to s[0]
Go strings are sequence (read-only slice) of bytes as a result these can’t be changed (immutable like Java). In case you want to change you should convert string to byte slice change and convert back to string.
mtx.Unlock undefined (type *** has no field or method Unlock)
Defining new type from an existing (non-interface) type does not inherit methods define on that struct. If you need these methods either use type embedding with anonymous name or initialize variable using actual struct and assign this to new type.
cannot call pointer method on ***
The operand must be addressable composite literal. In Go not every variable is addressable though, a map entry cannot be addressed (as its address might change during map growth / shrink).
syntax error: unexpected ++
Unlike Java, Go does not support prefix version of increment and decrement operation. Also Go does not allow use of these operators in the expression.
fatal error: all goroutines are asleep - deadlock!
Send and receive operations on a nil channel block forever and gives fatal error: all goroutines are asleep - deadlock! Inspite of a well-documented behaviour, this can surprise for new developers. To avoid such scenario, we should initialize a channel before using this.

Intermediate

These are little tricky as to fix as your program behave abruptly at runtime. We can address such Gotchas with little investigation because these are directly visible at runtime in form of unexpected behavior, panic etc.
Variable shadow
This is a very common trap even for experienced Go developers. It's easy to make and it could be hard to spot. You can make use of linters vet or go-nyet: More aggressive go vet to detect such errors in the program.
Zero sized variables
Shouldn’t two different variables have different addresses? Well, it's not the case with Go 😊, if you have zero-sized variables they might share the exact same address in memory.
Entry in Nil map
A nil map behaves like an empty map when reading, but attempts to write to a nil map will cause a runtime panic; don't do that. Always initialize a map, using the built-in make function.
Unexpected values in range
Another most common gotcha for the developer accustomed to a foreach loop in Java. In Go, the range clause is different, it generates two values: first value is the index and the second one is data.
Update reference item in range
The data values generated in the range clause are copies of the actual collection elements, these does not reference original item. Which means that updating the values will not change the original data. You should make use of index operator on collection to update original value.
Breaking Out of for switch
The break statement in the switch moves out of switch and not for loop. If you want to move out of for loop, one option is return but this may not be a solution every time another alternative is to use label in break statement (similar to goto statements)
Breaking Out of for select
In Go, select works similar to switch statement only difference is case statements should have a channel. The break statement in the select moves out of select and not for loop. If you want to move out of for loop, one option is return but this may not be a solution every time another alternative is to use label in break.
Data race – Closure and iteration variable
This is yet another most common pitfall in Go, the iteration variables in for statements are reused across iterations. Which means that each closure created in the for loop will reference the same variable. You can solve this by creating a local variable in the loop or other solution could be to pass variable as a parameter in the closure function. We can detect this early in the program by using Go Race Detector.
Recovering from a Panic
The recover function can be used to catch/intercept a panic. Calling recover will do the trick only when it's done in a deferred function. The call to recover works only if it's called directly in your deferred function.
Case statement is not executing
You might think we need to add break statement in the switch to move out, well this is quite different in Go, we don’t need to add break in the case block, as a result fall-through does not work as expected. You can force this in the case blocks to fall-through by using the fall-through statement at the end of each case block or rewrite switch statement to use expression lists in the case blocks.
Empty structs in the JSON text output
Only exported fields of a Go struct will be present in the JSON output. You can specify the JSON field name explicitly with a json: tag.
Nil is not equal to nil
In Go, an interface equals to another interface, only if the strong>concrete value and dynamic type are both nil. The same applies to nil value. You can think of the interface value nil as typed, and nil without type doesn’t equal nil with type. If we convert nil to the correct type, the values are indeed equal.
Why goroutine execution did not complete
In Go, application does not wait for all goroutines to complete before it exists. This is a most common mistake done by a developer in early days of learning Go. We can avoid this using WaitGroup a most common solution, it allows us to wait in the main for goroutines to complete. Another solution is to pass goroutine execution state using channel.
Working with closed channel
Receiving from a closed channel is safe, whereas writing on closed channel throws a panic. Second value received from the channel, indicates that is there more data to be received or not. This is a well-documented behavior, but it's not very intuitive for new Go developers who might expect the send behavior to be similar to the receive behavior. This is complex enough and needs quite a good amount of thought as this may be resolved with minor code change or may need change in design.
Invalid memory address or nil pointer dereference
Go developers many times faces the issue of dereferencing of a nil pointer. An uninitialized pointer is nil, and you can’t easily follow the nil pointer. If x is nil, an attempt to evaluate *x will cause a run-time panic.
The Trim function
The Trim, Trim Left and Trim Right functions strip all Unicode code points contained in a cut-set. To strip a trailing string, you should make use of Trim Suffix or Trim Prefix.

Advanced

These are the most complex Gotchas and needs good amount of digging to identify and address as they may not be directly visible in the program such as resource leak, blocking channel, deadlocks, routine leaks, infinite loop etc.
Iota does not always start with zero
At a first glance, it seems that iota identifier is an increment operator starts form zero and continues to increase from there, well this is not always true in Go. The value of iota is identified by an index operator for the current line in the constant declaration block, which means if the use of iota is not the first line in the constant declaration block the initial value may not be zero.
Array function arguments
As a Java developer, you may think, when you pass arrays to functions, the functions reference the same memory location, so they can update the original data. But it's different in Go, arrays are values and does not share memory location, so when you pass arrays to functions. They get their own copy of the original array data. This can be a problem, if you are trying to update the array data.
Non existing map keys
Map returns a zero-value of a type for non-existing keys. Checking for the appropriate zero value can be used to determine, if the map record exists or not, but it's not always reliable (e.g., what if you have a map of Boolean where the zero value is false). The most reliable way to know, if a given map record exists or not is to check the second value returned by the map access operation.
Hidden data in slices
The new slice created out of an existing slice will reference the array of the original slice. This behavior can lead to unexpected memory usage, if your application allocates large slices and creates new slices from them to refer to small sections of the original data. This can be avoided by making a copy of the data needed from the temporary slice.
Append can cause data corruption in Slice
In Go, slices are backed by an array and capacity as the original. So, if you change an element in the slice, the original contents are modified as well. At some time adding data to one of the slices may result in a to a new array allocation, if the original array can't hold more data as a result other slices will point to the old array (with old data) whereas this can point to new instance of slice. This can be avoided by making a copy of the data.
Unexpected value is being passed in differed function
Arguments for a deferred function call are evaluated at the time of defer statement evaluation and not at the time of function execution. You should make a use of pointer parameters to overcome this as pointer is saved at the time of defer statement is evaluation.
Wrong use of defer can cause resource leak
You might use a defer in the code block and think that this will be called at the end of function to clean resources. This can eventually cause a resource leak, if you are running a long for loop and calling defer in the code block to release resources. This can be avoided, by warping code block to function block or use direct clean-up instead of defer statement.
Why execution flow is blocked for channel
You might think the sender will not be blocked until your message is processed by the receiver. Depending on the machine where you are running the code, the receiver goroutine may or may not have enough time to process the message before the sender continues its execution.
Where is my copy
The copy function in the Go, copies minimum elements of the source to destination, to ensure correct coping, you should allocate sufficient destination slice. The copy function returns number of elements copied. Alternatively, you can use append function in Go to copy array elements, make a note that, size (capacity) of the append slice will be larger than the length of slice.

Final Thought

To all my gopher friends, I hope you found my collection of these gotchas will help you to learn from my common mistakes in Golang. Every Gotcha cannot be attacked by learning internals. Some of them are simply the difference between your past and new experience, and we’re all have somehow different background and experience. Nevertheless, there a many of the gotchas that can be successfully avoided simply by understanding a bit deeper how Go works. Do share your experience with such Gotchas in the comments or Git that are new to me, that I haven’t mentioned.
Every Gotcha needs to be attacked differently