Go, End to End
Types and the Type System
Go's built-in types are unremarkable on the surface (bool, the integer family, floats, string) but two decisions trip up people coming from almost any other language: conversions are never implicit, and plain int doesn't have a fixed, portable size.
Numeric types
Go gives you sized integers (int8 through int64, and unsigned uint8 through uint64), sized floats (float32, float64), and the plain int/uint, which are 32 or 64 bits depending on the platform, whatever is most efficient for that architecture. Most code should just use int unless there's a specific reason to care about the exact width, like a binary file format or a network protocol.
byte is an alias for uint8, and rune is an alias for int32 (a Unicode code point). You'll see both constantly once you start working with strings.
No implicit conversion, anywhere
This is the single biggest adjustment for newcomers. Go will not silently convert an int to a float64, or an int32 to an int64, even though the conversion is lossless. Every conversion is an explicit T(value) call.
This is deliberate, not an oversight. Implicit numeric conversion is a classic source of subtle bugs (silent precision loss, unexpected sign extension), and Go's designers decided the extra keystrokes are worth eliminating that entire bug class.
Type inference with :=
When you use :=, the compiler picks a type based on the right-hand side: integer literals become int, floating-point literals become float64, string literals become string. This inference happens once at declaration and the variable's type is fixed after that, Go isn't dynamically typed.
Type declarations: alias vs new named type
type Celsius = float64 creates an alias: Celsius and float64 are completely interchangeable, the same type under two names. type Celsius float64 (no =) creates a new, distinct named type with float64 as its underlying representation. The two look almost identical but behave very differently.
Named types matter because you can attach methods to them, and because the compiler will stop you from accidentally mixing values that are conceptually different even if they share a representation (a UserID int and an OrderID int won't be silently interchangeable, for example).
Try it yourself: define type Meters float64 and type Feet float64, write a function that adds two Meters values, then try passing a Feet value in without converting it. Watch the compiler reject it.