AlgoMaster Logo

Type Conversions

Last Updated: May 22, 2026

Medium Priority
6 min read

Go does not convert types for you. If you have an int and you need a float64, you write the conversion yourself. This lesson covers the T(v) syntax, why Go works this way, and the traps you'll hit when converting between numbers, strings, and byte slices.

Why Go Forbids Implicit Conversion

Most languages let you mix numeric types and silently figure out the rest. In Go, this code refuses to compile:

The compiler produces:

You probably expected 159.92, but Go won't let int and float64 interact without an explicit conversion. The reason is simple: implicit conversion hides precision loss and range errors. A language that quietly turns a 64-bit integer into a 32-bit float can produce wrong answers, and you'd never see them in code review. Go forces you to write the conversion so the cost is visible.

The fix is to convert one of the values:

The syntax is T(v): the target type as a function call, with the value inside. float64(priceInCents) produces a new float64 value. The original priceInCents is untouched.

Numeric Conversions

The most common conversions move between number types. The syntax is identical no matter which pair you're working with:

This is typical price math in an e-commerce app. Store money as integer cents to avoid floating-point rounding bugs, convert to float64 only when you need to display or compute with decimals.

Float to Int: Truncation, Not Rounding

Converting a float64 to an int drops the fractional part. It does not round.

int(4.7) is 4, not 5. int(-3.9) is -3, not -4. The fractional part is thrown away, and the result always moves toward zero. If you actually want rounding, use math.Round first:

Int Sizes: Overflow When Narrowing

Go has int8, int16, int32, int64, plus a platform-sized int (usually 64 bits on modern machines). Converting from a wider type to a narrower one is legal, but the value is truncated to fit.

Three billion fits in an int64 (which can hold roughly nine quintillion), but it doesn't fit in an int32 (max is about 2.1 billion). The conversion takes the low 32 bits of the value, and because the high bit ends up set, the result is a negative number. The compiler does not warn you. You wrote the conversion, so you accepted the risk.

If you need to detect overflow before converting, check the value against the target range from the math package:

Unsigned to Signed (and Back)

Go has uint8, uint16, uint32, uint64, and uint. Converting between signed and unsigned of the same width reinterprets the bits without changing them, which can flip the sign:

The two's-complement representation of -1 as an int8 is all 1 bits. Reinterpreted as a uint8, those same bits mean 255. This bites you when subtracting from a uint:

uint math wraps around. 5 - 8 becomes a gigantic number because uint can't represent negatives. For anything that can legitimately go negative (stock corrections, balance changes, deltas) use a signed type.

String Conversions: The Most Surprising Part

String conversions in Go look familiar but have a few non-obvious behaviors. There are two operations people often confuse: converting an integer to its textual representation (the digit string), and converting an integer to the character at that codepoint. They use different tools and produce different results.

The string(65) Trap

What's wrong with this code?

You probably expect Order #65. What you get is:

string(65) does not produce the digit string "65". It produces the string containing the single Unicode character at codepoint 65, which happens to be A. This is a real Go feature, not a bug. It's how you'd construct a string from a known rune value.

The fix is strconv.Itoa (integer to ASCII), which produces the textual representation:

The strconv package has the full set of number-to-text and text-to-number functions. For now remember the rule:

  • string(intValue) produces a string containing the rune at that codepoint.
  • strconv.Itoa(intValue) produces the textual digit string.

The Go compiler emits a vet warning when you pass a non-rune integer to string(...), but it still compiles. Treat the warning as an error in practice.

Parsing a String to an Int

The reverse direction comes up whenever you read user input. A customer types a quantity into a form, you get a string, you want a number:

strconv.Atoi returns two values: the parsed number and an error. The error is not optional. If the user typed "three" or "3.0" or left the field blank, the conversion fails, and ignoring the error would leave quantity at zero with no indication that something went wrong.

Here's what happens with bad input:

This is the difference between a conversion and a parse. int(someFloat) is a conversion: it can't fail, it just truncates. strconv.Atoi(someString) is a parse: it can fail, and you have to handle that.

Strings, Byte Slices, and Rune Slices

A Go string is, internally, an immutable sequence of bytes. You can convert between strings and byte slices in both directions:

[]byte("Hi") makes a new byte slice containing the bytes 72 and 105 (the ASCII codes for H and i). string([]byte{0x48, 0x69}) does the reverse, building a string from those bytes.

There's also []rune(s), which decodes the string's UTF-8 bytes into Unicode code points:

For pure ASCII the rune slice and byte slice contain the same numbers, but for any multi-byte character (accented letters, emoji, non-Latin scripts) they differ. For this lesson, just know that []byte(s) and []rune(s) are conversions, not parses. They never fail.

Untyped Constants Skip the Conversion

One place where Go is friendlier than the rules suggest is with untyped constants. A constant without a declared type can be used wherever its value fits:

taxRate is an untyped float constant. It can multiply with a float64 without any conversion on its side. The integer priceInCents still needs float64(...) because it's a typed variable, not a constant. This asymmetry between typed and untyped values is the reason i := 10; f := 2.5; x := i * f is an error while x := 10 * 2.5 is fine.

Common Conversions and Their Gotchas

The table below covers the conversions that come up most often and the surprises in each one.

FromToSyntaxGotcha
intfloat64float64(i)Safe for small ints. float32(largeInt) loses precision beyond 24 bits of mantissa.
float64intint(f)Truncates toward zero. int(4.9) is 4, int(-4.9) is -4. Use math.Round for true rounding.
int64int32int32(i)Silently truncates if the value doesn't fit. No panic, no warning.
int8uint8uint8(i)Same bits, different interpretation. -1 becomes 255.
uintuint matha - bUnderflows wrap to a huge value. Use signed types for anything that can go negative.
intstring (digits)strconv.Itoa(i)string(i) gives the rune at that codepoint, not the digits.
stringintstrconv.Atoi(s)Returns an error. You must check it.
string[]byte[]byte(s)Allocates and copies all bytes.
[]bytestringstring(b)Allocates and copies all bytes.
string[]rune[]rune(s)Decodes UTF-8 into code points. Length may differ from byte length.
[]runestringstring(r)Encodes code points back to UTF-8 bytes.

The string-and-bytes conversions are the only ones with real cost. Everything else is essentially a relabeling.

A Practical Example

Putting the pieces together, here's a small program that reads a quantity from a string (as if from a form), looks up a product price stored in cents, and prints the line total in dollars:

Three different conversions show up here. strconv.Atoi parses the string input. float64(totalCents) and float64(priceInCents) widen integers to floats for the dollar math. The error from Atoi is checked. If you removed the conversions, the code wouldn't compile. If you skipped the error check, a bad input would silently produce Line total: $0.00, which is the kind of bug that ships to production and embarrasses everyone.