and
Go Generics 101
are published now.
It is most cost-effective to buy all of them through
this book bundle
in the Leanpub book store.
Interface types are one special kind of type in Go.
Interfaces play several important roles in Go.
Fundamentally, interface types make Go support value boxing.
Consequently, through value boxing, reflection and polymorphism get supported.
Since version 1.18, Go has already supported custom generics.
In custom generics, an interface type could be (always) also used as type constraints.
In fact, all type constraints are actually interface types.
Before Go 1.18, all interface types may be used as value types.
But since Go 1.18, some interface types may be only used as type constraints.
Interface types which may be used as value types are called basic interface types.
This article was mainly written before Go supports custom generics,
so it mainly talks about basic interfaces.
About constraint-only interface types, please read the
Go generics 101
book for details.
Interface Types and Type Sets
An interface type defines some (type) requirements.
All non-interface types satisfying these requirements form a type set,
which is called the type set of the interface type.
The requirements defined for an interface type are expressed
by embedding some interface elements in the interface type.
Currently (Go 1.21), there are two kinds of interface elements, method elements and type elements.
A method element presents as a
method specification
.
A method specification embedded in an interface type may not
use the blank identifier
_
as its name.
A type element may be a type name, a type literal, an approximation type, or a type union.
The current article doesn't talk much about the latter two and only talks about
type names and literals which denote interface types.
For example, the predeclared
error
interface type
, which definition is shown below,
embeds a method specification
Error() string
.
In the definition,
interface{...}
is called an interface type literal
and the word
interface
are a keyword in Go.
type error interface {
Error() string
We may also say the error
interface type (directly) specified a method Error() string
.
Its type set is composed of all non-interface types
which have a method with the specification Error() string
.
In theory, the type set is infinite.
Surely, for a specified Go project, it is finite.
The following are some other interface type definitions and alias declarations.
// This interface directly specifies two methods and
// embeds two other interface types, one of which
// is a type name and the other is a type literal.
type ReadWriteCloser = interface {
Read(buf []byte) (n int, err error)
Write(buf []byte) (n int, err error)
error // a type name
interface{ Close() error } // a type literal
// This interface embeds an approximation type. Its type
// set includes all types whose underlying type is []byte.
type AnyByteSlice = interface {
~[]byte
// This interface embeds a type union. Its type set includes
// 6 types: uint, uint8, uint16, uint32, uint64 and uintptr.
type Unsigned interface {
uint | uint8 | uint16 | uint32 | uint64 | uintptr
Embedding an interface type (denoted by either a type name or a type literal)
in another one is equivalent to (recursively) expanding the elements in the former into the latter.
For example, the interface type denoted by the type alias ReadWriteCloser
is equivalent to the interface type denoted by the following literal,
which directly specifies four methods.
interface {
Read(buf []byte) (n int, err error)
Write(buf []byte) (n int, err error)
Error() string
Close() error
The type set of the above interface type is composed of all non-interface types
which at least have the four methods specified by the interface type.
The type set is also infinite.
It is definitely a subset of the type set of error
interface type.
Please note that, before Go 1.18, only interface type names may be embedded in interface types.
The interface types shown in the following code are all called blank interface types,
which embeds nothing.
// The unnamed blank interface type.
interface{}
// Nothing is a defined blank interface type.
type Nothing interface{}
In fact, Go 1.18 introduced a predeclared alias, any
,
which denotes the blank interface type interface{}
.
The type set of a blank interface type is composed of all non-interface types.
For a non-interface type, its method set is composed of the specifications of
all the methods (either explicit or implicit ones) declared
for it.
For an interface type, its method set is composed of all the method specifications
it specifies, either directly or indirectly through embedding other types.
In the examples shown in the last section,
the method set of the interface type denoted by ReadWriteCloser
contains four methods.
the method set of the predeclared interface type error
contains only one method.
the method set of a blank interface type is empty.
Currently (Go 1.21), every basic interface type could be defined entirely
by a method set (may be empty).
In other words, a basic interface type doesn't need type elements to be defined.
In the examples shown in the section before the last, the interface type denoted
by alias ReadWriteCloser
is a basic type,
but the Unsigned
interface type and the type denoted by alias
AnyByteSlice
are not.
The latter two are both constraint-only interface types.
Blank interface types and the predeclared error
interface type
are also all basic interface types.
Two unnamed basic interface types are identical if their method sets are identical.
Please note, non-exported method names (which start with lower-case letters),
from different packages will be always viewed as two different method names,
even if the two method names themselves are the same.
If a non-interface type is contained in the type set of an interface type,
then we say the non-interface type implements the interface type.
If the type set of an interface type is a subset of another interface type,
then we say the former one implements the latter one.
An interface type always implements itself, as a type set is always a subset (or superset) of itself.
Similarly, two interface types with the same method set implement each other.
In fact, two unnamed interface types are identical if their type sets are identical.
If a type T
implements an interface type X
,
then the method set of T
must be superset of X
,
whether T
is an interface type or an non-interface type.
Generally, not vice versa.
But if X
is a basic interface, then vice versa.
For example, in the examples provided in a previous section,
the interface type denoted by ReadWriteCloser
implements the error
interface type.
Implementations are all implicit in Go.
The compiler does not require implementation relations to be specified in code explicitly.
There is not an implements
keyword in Go.
Go compilers will check the implementation relations automatically as needed.
For example, in the following example, the method sets of
struct pointer type *Book
, integer type MyInt
and
pointer type *MyInt
all contain the method specification
About() string
, so they all implement the above mentioned
interface type Aboutable
.
type Aboutable interface {
About() string
type Book struct {
name string
// more other fields ...
func (book *Book) About() string {
return "Book: " + book.name
type MyInt int
func (MyInt) About() string {
return "I'm a custom integer value"
The implicit implementation design makes it possible to let types
defined in other library packages, such as standard packages,
passively implement some interface types declared in user packages.
For example, if we declare an interface type as the following one,
then the type DB
and type Tx
declared in
the database/sql
standard package will both implement the interface type automatically,
for they both have the three corresponding methods specified in the interface.
import "database/sql"
type DatabaseStorer interface {
Exec(query string, args ...interface{}) (sql.Result, error)
Prepare(query string) (*sql.Stmt, error)
Query(query string, args ...interface{}) (*sql.Rows, error)
Note, as the type set of a blank interface type is composed of all non-interface types,
so all types implement any blank interface type.
This is an important fact in Go.
Again, currently (Go 1.21), the types of interface values must be basic interface types.
In the remaining contents of the current article,
when a value type is mentioned, the value type may be
non-interface type or a basic interface type.
It is never a constraint-only interface type.
We can view each interface value as a box to encapsulate a non-interface value.
To box/encapsulate a non-interface value into an interface value,
the type of the non-interface value must implement the type of the interface value.
If a type T
implements a (basic) interface type I
,
then any value of type T
can be implicitly converted to type I
.
In other words, any value of type T
is
assignable
to (modifiable) values of type I
.
When a T
value is converted (assigned) to an I
value,
if type T
is a non-interface type, then
a copy of the T
value is boxed (or encapsulated)
into the result (or destination) I
value.
The time complexity of the copy is O(n)
,
where n
is the size of copied T
value.
if type T
is also an interface type, then
a copy of the value boxed in the T
value is boxed
(or encapsulated) into the result (or destination) I
value.
The standard Go compiler makes an optimization here,
so the time complexity of the copy is O(1)
,
instead of O(n)
.
The type information of the boxed value is also stored in the result (or destination) interface value.
(This will be further explained below.)
When a value is boxed in an interface value, the value is called
the dynamic value of the interface value.
The type of the dynamic value is called the
dynamic type of the interface value.
The direct part of the dynamic value of an interface value is immutable,
though we can replace the dynamic value of an interface value with another dynamic value.
In Go, the zero values of any interface type are represented
by the predeclared nil
identifier.
Nothing is boxed in a nil interface value.
Assigning an untyped nil
to an interface value will
clear the dynamic value boxed in the interface value.
(Note, the zero values of many non-interface types in Go
are also represented by nil
in Go.
Non-interface nil values can also be boxed in interface values.
An interface value boxing a nil non-interface value still boxes something,
so it is not a nil interface value.)
As any type implements all blank interface types,
so any non-interface value can be boxed in (or assigned to) a blank interface value.
For this reason, blank interface types can be viewed as the
any
type in many other languages.
When an untyped value (except untyped nil
values)
is assigned to a blank interface value,
the untyped value will be first converted to its default type.
(In other words, we can think the untyped value is deduced as a value of its default type).
Let's view an example which demonstrates some assignments
with interface values as the destinations.
package main
import "fmt"
type Aboutable interface {
About() string
// Type *Book implements Aboutable.
type Book struct {
name string
func (book *Book) About() string {
return "Book: " + book.name
func main() {
// A *Book value is boxed into an
// interface value of type Aboutable.
var a Aboutable = &Book{"Go 101"}
fmt.Println(a) // &{Go 101}
// i is a blank interface value.
var i interface{} = &Book{"Rust 101"}
fmt.Println(i) // &{Rust 101}
// Aboutable implements interface{}.
i = a
fmt.Println(i) // &{Go 101}
Please note, the prototype of the fmt.Println
function used many times in previous articles is
func Println(a ...interface{}) (n int, err error)
This is why a fmt.Println
function calls can take arguments of
any types.
The following is another example which shows how a blank interface value
is used to box values of any non-interface type.
package main
import "fmt"
func main() {
var i interface{}
i = []int{1, 2, 3}
fmt.Println(i) // [1 2 3]
i = map[string]int{"Go": 2012}
fmt.Println(i) // map[Go:2012]
i = true
fmt.Println(i) // true
i = 1
fmt.Println(i) // 1
i = "abc"
fmt.Println(i) // abc
// Clear the boxed value in interface value i.
i = nil
fmt.Println(i) // <nil>
Go compilers will build a global table which contains
the information of each type at compile time.
The information includes
what kind a type is,
what methods and fields a type owns,
what the element type of a container type is, type sizes, etc.
The global table will be loaded into memory when a program starts.
At run time, when a non-interface value is boxed into an interface value,
the Go runtime (at least for the standard Go runtime) will analyze and build
the implementation information for the type pair of the two values,
and store the implementation information in the interface value.
The implementation information for each non-interface type and interface type pair will only be
built once and cached in a global map for execution efficiency consideration.
The number of entries of the global map never decreases.
In fact, a non-nil interface value just uses an internal pointer field
which references a cached implementation information entry.
The implementation information for each (interface type, dynamic type) pair
includes two pieces of information:
the information of the dynamic type (a non-interface type)
and a method table (a slice) which stores all the corresponding methods specified
by the interface type and declared for the non-interface type (the dynamic type).
These two pieces of information are essential for implementing two important features in Go:
The dynamic type information is the key to implement reflection in Go.
The method table information is the key to implement polymorphism (polymorphism will be explained in the next section).
When a non-interface value t
of a type T
is boxed in an interface value i
of type I
,
calling a method specified by the interface type I
on the interface value i
will call the corresponding method declared for the non-interface type T
on the non-interface value t
actually.
In other words, calling the method of an interface value will actually
call the corresponding method of the dynamic value of the interface value.
For example, calling method i.m
will call method t.m
actually.
With different dynamic values of different dynamic types boxed into the interface value,
the interface value behaves differently. This is called polymorphism.
When method i.m
is called, the method table in the implementation information
stored in i
will be looked up to find and call the corresponding method t.m
.
The method table is a slice and the lookup is just a slice element indexing, so this is quick.
(Note, calling methods on a nil interface value will panic at run time,
for there are no available declared methods to be called.)
An example:
package main
import "fmt"
type Filter interface {
About() string
Process([]int) []int
// UniqueFilter is used to remove duplicate numbers.
type UniqueFilter struct{}
func (UniqueFilter) About() string {
return "remove duplicate numbers"
func (UniqueFilter) Process(inputs []int) []int {
outs := make([]int, 0, len(inputs))
pusheds := make(map[int]bool)
for _, n := range inputs {
if !pusheds[n] {
pusheds[n] = true
outs = append(outs, n)
return outs
// MultipleFilter is used to keep only
// the numbers which are multiples of
// the MultipleFilter as an int value.
type MultipleFilter int
func (mf MultipleFilter) About() string {
return fmt.Sprintf("keep multiples of %v", mf)
func (mf MultipleFilter) Process(inputs []int) []int {
var outs = make([]int, 0, len(inputs))
for _, n := range inputs {
if n % int(mf) == 0 {
outs = append(outs, n)
return outs
// With the help of polymorphism, only one
// "filterAndPrint" function is needed.
func filterAndPrint(fltr Filter, unfiltered []int) []int {
// Calling the methods of "fltr" will call the
// methods of the value boxed in "fltr" actually.
filtered := fltr.Process(unfiltered)
fmt.Println(fltr.About() + ":\n\t", filtered)
return filtered
func main() {
numbers := []int{12, 7, 21, 12, 12, 26, 25, 21, 30}
fmt.Println("before filtering:\n\t", numbers)
// Three non-interface values are boxed into
// three Filter interface slice element values.
filters := []Filter{
UniqueFilter{},
MultipleFilter(2),
MultipleFilter(3),
// Each slice element will be assigned to the
// local variable "fltr" (of interface type
// Filter) one by one. The value boxed in each
// element will also be copied into "fltr".
for _, fltr := range filters {
numbers = filterAndPrint(fltr, numbers)
The output:
before filtering:
[12 7 21 12 12 26 25 21 30]
remove duplicate numbers:
[12 7 21 26 25 30]
keep multiples of 2:
[12 26 30]
keep multiples of 3:
[12 30]
In the above example, polymorphism makes it unnecessary to
write one filterAndPrint
function for each filter type.
Besides the above benefit, polymorphism also makes it possible for the developers
of a library code package to declare an exported interface type and declare a function (or method)
which has a parameter of the interface type, so that a user of the package
can declare a type, which implements the interface type, in user code and
pass arguments of the user type to calls to the function (or method).
The developers of the code package don't need to care about how the user type is declared,
as long as the user type satisfies the behaviors specified
by the interface type declared in the library code package.
In fact, polymorphism is not an essential feature for a language.
There are alternative ways to achieve the same job, such as callback functions.
But the polymorphism way is cleaner and more elegant.
Reflection
The dynamic type information stored in an interface value
can be used to inspect the dynamic value of the interface value
and manipulate the values referenced by the dynamic value.
This is called reflection in programming.
This article will not explain the functionalities provided by
the reflect
standard package.
Please read reflections in Go
to get how to use that package.
Below will only introduce the built-in reflection functionalities in Go.
In Go, built-in reflections are achieved with type assertions and type-switch
control flow code blocks.
Type assertion
There are four kinds of interface-value-involving value conversion cases in Go:
convert a non-interface value to an interface value,
where the type of the non-interface value
must implement the type of the interface value.
convert an interface value to an interface value,
where the type of the source interface value
must implement the type of the destination interface value.
convert an interface value to a non-interface value,
where the type of the non-interface value
must implement the type of the interface value.
convert an interface value to an interface value,
where the type of the source interface value
doesn't implement the destination interface type,
but the dynamic type of the source interface value might
implement the destination interface type.
We have already explained the first two kinds of cases.
They both require that the source value type implements the destination interface type.
The convertibility for the first two are verified at compile time.
Here will explain the later two kinds of cases.
The convertibility for the later two are verified at run time,
by using a syntax called type assertion.
In fact, the syntax also applies to the second kind of conversion in our above list.
The form of a type assertion expression is i.(T)
,
where i
is an interface value and T
is a type name or a type literal.
Type T
must be
either an arbitrary non-interface type,
or an arbitrary interface type.
In a type assertion i.(T)
,
i
is called the asserted value
and T
is called the asserted type.
A type assertion might succeed or fail.
In the case of T
being a non-interface type,
if the dynamic type of i
exists and is identical to T
, then the assertion will succeed,
otherwise, the assertion will fail.
When the assertion succeeds, the evaluation result of the assertion is a copy of the dynamic value of i
.
We can view assertions of this kind as value unboxing attempts.
In the case of T
being an interface type,
if the dynamic type of i
exists and implements T
,
then the assertion will succeed, otherwise, the assertion will fail.
When the assertion succeeds, a copy of the dynamic value of i
will be boxed into a T
value
and the T
value will be used as the evaluation result of the assertion.
For most scenarios, a type assertion is used as a single-value expression.
However, when a type assertion is used as the only source value expression
in an assignment, it can result in a second optional untyped boolean value
and be viewed as a multi-value expression.
The second optional untyped boolean value indicates whether or not the type assertion succeeds.
Note, if a type assertion fails and the type assertion is used as a
single-value expression (the second optional bool result is absent),
then a panic will occur.
An example which shows how to use type assertions (asserted types are non-interface types):
package main
import "fmt"
func main() {
// Compiler will deduce the type of 123 as int.
var x interface{} = 123
// Case 1:
n, ok := x.(int)
fmt.Println(n, ok) // 123 true
n = x.(int)
fmt.Println(n) // 123
// Case 2:
a, ok := x.(float64)
fmt.Println(a, ok) // 0 false
// Case 3:
a = x.(float64) // will panic
Another example which shows how to use type assertions (asserted types are interface types):
package main
import "fmt"
type Writer interface {
Write(buf []byte) (int, error)
type DummyWriter struct{}
func (DummyWriter) Write(buf []byte) (int, error) {
return len(buf), nil
func main() {
var x interface{} = DummyWriter{}
var y interface{} = "abc"
// Now the dynamic type of y is "string".
var w Writer
var ok bool
// Type DummyWriter implements both
// Writer and interface{}.
w, ok = x.(Writer)
fmt.Println(w, ok) // {} true
x, ok = w.(interface{})
fmt.Println(x, ok) // {} true
// The dynamic type of y is "string",
// which doesn't implement Writer.
w, ok = y.(Writer)
fmt.Println(w, ok) // <nil> false
w = y.(Writer) // will panic
The type-switch
code block syntax may be the weirdest syntax in Go.
It can be viewed as the enhanced version of type assertion.
A type-switch
code block is in some way similar to a switch-case
control flow code block.
It looks like:
switch aSimpleStatement; v := x.(type) {
case TypeA:
case TypeB, TypeC:
case nil:
default:
The aSimpleStatement;
portion is optional in a type-switch
code block.
aSimpleStatement
must be a simple statement.
x
must be an interface value and it is called the asserted value.
v
is called the assertion result, it must be present in a short variable declaration form.
Each case
keyword in a type-switch
block can be followed by
the predeclared nil
identifier or a comma-separated list composed of at least one type name and type literal.
None of such items (nil
, type names and type literals) may be duplicate in the same type-switch
code block.
If the type denoted by a type name or type literal following a case
keyword
in a type-switch
code block is not an interface type, then it
must implement the interface type of the asserted value.
Here is an example in which a type-switch
control flow code block is used.
package main
import "fmt"
func main() {
values := []interface{}{
456, "abc", true, 0.33, int32(789),
[]int{1, 2, 3}, map[int]bool{}, nil,
for _, x := range values {
// Here, v is declared once, but it denotes
// different variables in different branches.
switch v := x.(type) {
case []int: // a type literal
// The type of v is "[]int" in this branch.
fmt.Println("int slice:", v)
case string: // one type name
// The type of v is "string" in this branch.
fmt.Println("string:", v)
case int, float64, int32: // multiple type names
// The type of v is "interface{}",
// the same as x in this branch.
fmt.Println("number:", v)
case nil:
// The type of v is "interface{}",
// the same as x in this branch.
fmt.Println(v)
default:
// The type of v is "interface{}",
// the same as x in this branch.
fmt.Println("others:", v)
// Note, each variable denoted by v in the
// last three branches is a copy of x.
The output:
number: 456
string: abc
others: true
number: 0.33
number: 789
int slice: [1 2 3]
others: map[]
values := []interface{}{
456, "abc", true, 0.33, int32(789),
[]int{1, 2, 3}, map[int]bool{}, nil,
for _, x := range values {
if v, ok := x.([]int); ok {
fmt.Println("int slice:", v)
} else if v, ok := x.(string); ok {
fmt.Println("string:", v)
} else if x == nil {
v := x
fmt.Println(v)
} else {
_, isInt := x.(int)
_, isFloat64 := x.(float64)
_, isInt32 := x.(int32)
if isInt || isFloat64 || isInt32 {
v := x
fmt.Println("number:", v)
} else {
v := x
fmt.Println("others:", v)
Like switch-case
blocks, in a type-switch
code block,
there can be at most one default
branch.
Like switch-case
blocks, in a type-switch
code block,
if the default
branch is present,
it can be the last branch, the first branch, or a middle branch.
Like switch-case
blocks, a type-switch
code block may not contain any branches,
it will be viewed as a no-op.
For the first case,
the type of the non-interface value must implement the type
(assume it is I
) of the interface value,
so the non-interface value can be converted to (boxed into)
an interface value of I
.
This means a comparison between a non-interface value and an interface value
can be translated to a comparison between two interface values.
So below only comparisons between two interface values will be explained.
Comparing two interface values is comparing their respective dynamic types
and dynamic values actually.
The steps of comparing two interface values (with the ==
operator):
if one of the two interface values is a nil interface value,
then the comparison result is whether or not
the other interface value is also a nil interface value.
if the dynamic types of the two interface values are two different types,
then the comparison result is false
.
in the case where the dynamic types of the two interface values are the same type,
if the same dynamic type is an
incomparable type,
a panic will occur.
otherwise, the comparison result is the result of comparing the dynamic values of the two interface values.
In short, two interface values are equal only if one of the following conditions are satisfied.
They are both nil interface values.
Their dynamic types are identical and comparable, and their dynamic values are equal to each other.
By the rules, two interface values which dynamic values are both nil
may be not equal. An example:
package main
import "fmt"
func main() {
var a, b, c interface{} = "abc", 123, "a"+"b"+"c"
// A case of step 2.
fmt.Println(a == b) // false
// A case of step 3.
fmt.Println(a == c) // true
var x *int = nil
var y *bool = nil
var ix, iy interface{} = x, y
var i interface{} = nil
// A case of step 2.
fmt.Println(ix == iy) // false
// A case of step 1.
fmt.Println(ix == i) // false
// A case of step 1.
fmt.Println(iy == i) // false
// []int is an incomparable type
var s []int = nil
i = s
// A case of step 1.
fmt.Println(i == nil) // false
// A case of step 3.
fmt.Println(i == i) // will panic
The internal structure of interface values
For the official Go compiler/runtime, blank interface values and non-blank interface values
are represented with two different internal structures.
Please read value parts for details.
The official Go compiler/runtime makes an optimization
which makes boxing pointer values into interface values
more efficient than boxing non-pointer values.
For small size values,
the efficiency differences are small,
but for large size values, the differences may be not small.
For the same optimization, type assertions with a pointer type
are also more efficient than type assertions with the base type
of the pointer type if the base type is a large size type.
So please try to avoid boxing large size values, box their pointers instead.
Values of []T
can't be directly converted to []I
,
even if type T
implements interface type I
.
For example, sometimes, we may need to convert a []string
value
to []interface{}
type. Unlike some other languages, there is no
direct way to make the conversion. We must make the conversion manually in a loop:
package main
import "fmt"
func main() {
words := []string{
"Go", "is", "a", "high",
"efficient", "language.",
// The prototype of fmt.Println function is
// func Println(a ...interface{}) (n int, err error).
// So words... can't be passed to it as the argument.
// fmt.Println(words...) // not compile
// Convert the []string value to []interface{}.
iw := make([]interface{}, 0, len(words))
for _, w := range words {
iw = append(iw, w)
fmt.Println(iw...) // compiles okay
Each method specified in an interface type corresponds to an implicit function
For each method with name m
in the method set defined by an interface type I
,
compilers will implicitly declare a function named I.m
,
which has one more input parameter, of type I
, than method m
.
The extra parameter is the first input parameter of function I.m
.
Assume i
is an interface value of I
,
then the method call i.m(...)
is equivalent to the function call I.m(i, ...)
.
An example:
package main
import "fmt"
type I interface {
m(int)bool
type T string
func (t T) m(n int) bool {
return len(t) > n
func main() {
var i I = T("gopher")
fmt.Println(i.m(5)) // true
fmt.Println(I.m(i, 5)) // true
fmt.Println(interface{m(int)bool}.m(i, 5)) // true
// The following lines compile okay,
// but will panic at run time.
I(nil).m(5)
I.m(nil, 5)
interface {m(int) bool}.m(nil, 5)
Github.
Welcome to improve Go 101 articles
by submitting corrections for all kinds of mistakes,
such as typos, grammar errors, wording inaccuracies,
description flaws, code bugs and broken links.
If you would like to learn some Go details and facts every serveral days,
please follow Go 101's official Twitter account @go100and1.
Tapir, the author of Go 101, has been on writing the Go 101 series books
and maintaining the go101.org website since 2016 July.
New contents will be continually added to the book and the website from time to time.
Tapir is also an indie game developer.
You can also support Go 101 by playing Tapir's games
(made for both Android and iPhone/iPad):
Color Infection (★★★★★), a physics based original casual puzzle game. 140+ levels.
Rectangle Pushers (★★★★★), an original casual puzzle game. Two modes, 104+ levels.
Let's Play With Particles, a casual action original game. Three mini games are included.
Introduction of Source Code Elements
Keywords and Identifiers
Basic Types and Their Value Literals
Constants and Variables - also introduces untyped values and type deductions.
Common Operators - also introduces more type deduction rules.
Function Declarations and Calls
Code Packages and Package Imports
Expressions, Statements and Simple Statements
Basic Control Flows
Goroutines, Deferred Function Calls and Panic/Recover
Structs
Value Parts - to gain a deeper understanding into Go values.
Arrays, Slices and Maps - first-class citizen container types.
Strings
Functions - function types and values, including variadic functions.
Channels - the Go way to do concurrency synchronizations.
Methods
Interfaces - value boxes used to do reflection and polymorphism.
Type Embedding - type extension in the Go way.
Type-Unsafe Pointers
Generics - use and read composite types
Reflections - the reflect
standard package.
More About Deferred Function Calls
Some Panic/Recover Use Cases
Explain Panic/Recover Mechanism in Detail - also explains exiting phases of function calls.
Code Blocks and Identifier Scopes
Expression Evaluation Orders
Value Copy Costs in Go
Bounds Check Elimination
Channel Use Cases
How to Gracefully Close Channels
Other Concurrency Synchronization Techniques - the sync
standard package.
Atomic Operations - the sync/atomic
standard package.
Memory Order Guarantees in Go
Common Concurrent Programming Mistakes