Go Brain Teasers

These are sample chapters from the book Go Brain Teasers: 25 brain teasers to tickle your mind and help you master the Go programming language by Miki Tebeka.

Buy the book at Gumroad (ePub & PDF) or Amazon (Kindle & dead tree).

Foreword by David Cheney

As a fan of trivia, in the salad days of my education as a software engineer, one of my favourite books was Josh Bloch and Neal Gafter’s Java Puzzlers. I liked that the authors didn’t simply set out to stump readers with the obscure language factoids. Instead, each question was treated as an opportunity to educate the reader on the history and deeper meaning of a less travelled aspect of the language.

When I discovered, far too many years into my Go experience than I care to mention, that copy returned the number of elements copied, my first thought was wow, how had I missed that. My second thought was I wonder how many people I can trick with this. Thus was born my #golang pop quiz series of tweets.

With tweet sizes being what they are the requirement to fit an entire Go program into a single tweet proved a challenge. Divining the correct answer to a #golang pop quiz and the opportunity to follow Bloch and Gafter’s example to educate, rather than frustrate, was left as an exercise to the reader.

In Go Brain Teasers, Miki has prepared a set of tests that seek not to simply baffle, but to enlighten. Go Brain Teasers' provides the reader with the opportunity to learn the why behind that what!?!.

David Cheney
Sydney, April 2020

The Brain Teasers

Eduction is not the learning of facts, but the training of the mind to think.
— Albert Einstein

1. Just in Time

time_eq.go
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
package main

import (
	"encoding/json"
	"fmt"
	"log"
	"time"
)

func main() {
	t1 := time.Now()
	data, err := json.Marshal(t1)
	if err != nil {
		log.Fatal(err)
	}

	var t2 time.Time
	if err := json.Unmarshal(data, &t2); err != nil {
		log.Fatal(err)
	}
	fmt.Println(t1 == t2)
}
Try to guess what the output is before moving to the next page.

This code will print: false

You might expect this code to fail since there’s no time type in the JSON format, or you might expect the comparison to succeed.

Go’s encoding/json lets you define custom JSON serialization for types that are not supported by JSON. You do that by implementing json.Marshaler and json.Unmarshaler interfaces. Go’s time.Time implements these interfaces by marshaling itself to an RFC 3339 formatted string and back.

JSON has a very limited set of types. For example it only has floating-point numbers. Which can lead to surprising results if you use the empty interface when unmarshaling.

json_float.go
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
package main

import (
	"encoding/json"
	"fmt"
	"log"
)

func main() {
	n1 := 1
	data, err := json.Marshal(n1)
	if err != nil {
		log.Fatal(err)
	}

	var n2 interface{}
	if err := json.Unmarshal(data, &n2); err != nil {
		log.Fatal(err)
	}

	fmt.Printf("n1 is %T, n2 is %T\n", n1, n2)
}

The above will print n1 is int, n2 is float64

Once you passed JSON serialization, you’d expect the times to be equal. The time package documentation says (my emphasis):

Operating systems provide both a "wall clock," which is subject to changes for clock synchronization, and a "monotonic clock," which is not. The general rule is that the wall clock is for telling time and the monotonic clock is for measuring time. Rather than split the API, in this package the Time returned by time.Now contains both a wall clock reading and a monotonic clock reading;

Monotonic clocks are used for measuring durations. They exist to avoid problems such as your computer switching to daylight saving time during measurement. The value of a monotonic clock by itself does not mean anything, only the difference between two monotonic clock readings is useful.

When you use == to compare time.Time, Go will compare the time.Time struct fields, including the monotonic reading. However when Go serializes a time.Time to JSON, it doesn’t include the monotonic clock in the output. When we read back the time to t2, it doesn’t contain the monotonic reading and the comparison fails.

The solution to the problem is written in time.Time’s documentation

In general, prefer t.Equal(u) to t == u, since t.Equal uses the most accurate comparison available and correctly handles the case when only one of its arguments has a monotonic clock reading.

1.1. Further Reading

2. A Funky Number?

num.go
1
2
3
4
5
6
7
8
9
package main

import (
	"fmt"
)

func main() {
	fmt.Println(0x1p-2)
}
Try to guess what the output is before moving to the next page.

This code will print: 0.25

Go has several number types, the two main ones are:

Integers

These are whole numbers. Go has int8, int16, int32, int64 and int.[1]. There are also all the unsigned ones uint8…​

Floats

These are real numbers. Go has float32 and float64.

There are other types such as complex, and the various types defined in math/big.

When you write a number literal, such as 3.14, the Go compiler needs to parse it to a specific type (float64 in this case). The Go spec defines how you can write numbers. Let’s have a look at some examples:

num_lit.go
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package main

import (
	"fmt"
)

func main() {
	// Integer
	printNum(10)    // 10 of type int
	printNum(010)   // 8 of type int
	printNum(0x10)  // 16 of type int
	printNum(0b10)  // 2 of type int
	printNum(1_000) // 1000 of type int (1)

	// Float
	printNum(3.14)   // 3.14 of type float64
	printNum(.2)     // 0.2 of type float64
	printNum(1e3)    // 1000 of type float64 (2)
	printNum(0x1p-2) // 0.25 of type float64 (3)

	// Complex
	printNum(1i)     // (0+1i) of type complex128
	printNum(3 + 7i) // (3+7i) of type complex128
	printNum(1 + 0i) // (1+0i) of type complex128
}

func printNum(n interface{}) {
	fmt.Printf("%v of type %T\n", n, n)
}
1 _ serves as the thousands separator. It makes big numbers much more readable for us humans.
2 This is known as scientific notation
3 The current brain teaser

0x1p-2 is called "a hexadecimal floating-point literal" in the Go specification and is following the IEEE 754 2008 specification. To calculate the value:

  • Compute the value before the p as a hexadecimal number. In this example it’s: 0x1 [2] = 1

  • Compute the value after the p as "2 to the power of that value". In this example it’s: 2-2 = 0.25 [3]

  • Finally multiply the two numbers. In this example: 1 * 0.25 = 0.25


1. int is an alias to your system integer size, 64 on my machine.
2. One in hexadecimal.
3. Remember your high school math: 2-2 = 1/22.