Literals

A detailed guide to integer, floating-point, imaginary, rune, and string literals in Go, covering syntax, base prefixes, underscores, escape sequences, and common pitfalls.

A literal is a value written directly in source code — a number like 42, a string like "hello", or a character like 'A'. The compiler reads the literal and turns it into a value your program can use without any computation. Every programming language has literals; Go’s are precise about what’s allowed, and that precision prevents entire categories of bugs.

This page covers the five literal kinds in Go: integers, floating‑point numbers, imaginary numbers, runes, and strings. Each section explains the syntax, the rules, and the mistakes beginners make most often.

Integer Literals

An integer literal is a whole number written directly in code, like 42 or 0b1010. Go supports four number bases: decimal, binary, octal, and hexadecimal. The base is indicated by a prefix on the literal.

Decimal, binary, octal, and hexadecimal

  • Decimal (base 10) has no prefix. The literal can be a single 0 or a sequence of digits that does not start with 0 (unless the whole literal is just 0). That rule prevents ambiguity with the octal prefix.
  • Binary (base 2) uses the prefix 0b or 0B, followed by only 0 and 1 digits.
  • Octal (base 8) uses the prefix 0, 0o, or 0O, followed by digits 0 through 7. The bare‑0 prefix (without o) is the original form and remains valid; the 0o form was added for clarity and to match the pattern of 0b and 0x.
  • Hexadecimal (base 16) uses the prefix 0x or 0X, followed by digits 09 and letters af or AF.

The following snippet declares the same value, 42, in all four bases:

d := 42       // decimal
b := 0b101010 // binary
o := 0o52     // octal (the o is lowercase)
h := 0x2A     // hexadecimal

All four variables hold the integer 42 — the base only changes how you write it, not the stored value. The compiler normalises them to the same internal representation.

Underscores for readability

Long numeric literals become hard to scan. Go lets you insert underscore characters _ after the base prefix and between digits to group them. Underscores are ignored by the compiler and exist only to help human eyes.

million := 1_000_000
flags   := 0b1100_0011_1010_0101
bigHex  := 0x_1a2b_3c4d

The underscores must separate digits — you cannot place one at the very start, the very end, or next to another underscore.

Compile‑time error:

These placements are illegal and will stop compilation:

  • _42 — underscore before the first digit looks like an identifier, not a number.
  • 42_ — trailing underscore suggests a missing digit.
  • 4__2 — consecutive underscores are never allowed.
  • 0x_BadFace — underscore immediately after the prefix without a digit in between.

A mental model that helps: the compiler first checks the prefix, then reads digits until it hits something that isn’t a digit or underscore. If an underscore appears where a digit would make no sense, the literal is invalid.

Zero is always decimal:

The literal 0 is a decimal zero. 0b0, 0o0, and 0x0 all represent the same zero but are technically written in binary, octal, or hexadecimal form. In practice, you can use plain 0 anywhere.

What beginners often miss

  • The bare‑0 prefix for octal surprises people who expect 0123 to be decimal 123. It’s actually octal, which equals decimal 83. If you mean decimal, do not start the literal with 0 unless you follow it with o or use it for the literal 0 itself.
  • Letters in hexadecimal are case‑insensitive, so 0xBad and 0xBAD are the same number.
  • Binary literals require at least one digit after the prefix. 0b alone is not a valid literal.

Floating-Point Literals

Floating‑point literals represent numbers with fractional parts. Go provides two forms: decimal floating‑point and hexadecimal floating‑point, mirroring the two ways floating‑point values can be expressed in hardware.

Decimal floating‑point

A decimal floating‑point literal has up to three parts: an integer part, a fractional part, and an exponent. You must keep at least one of the integer part or the fractional part; the exponent is optional. The decimal point is written with a dot . and the exponent with e or E, followed by an optional sign and decimal digits. The exponent scales the number by a power of 10.

var a = 3.14       // integer part and fraction
var b = .5         // fraction only, integer part omitted
var c = 1.         // integer part only, fraction omitted
var d = 6.022e23   // Avogadro’s number
var e = 1e-9       // exponent only, no decimal point

6.022e23 means 6.022 × 10^23. 1e-9 means 1 × 10^-9.

Hexadecimal floating‑point

A hexadecimal floating‑point literal starts with 0x or 0X, has an integer part and fractional part in hex, and must end with an exponent part using p or P (followed by optional sign and decimal digits). The p exponent scales the mantissa by a power of 2, not 10. This syntax directly represents the internal IEEE‑754 representation, making it precise for certain low‑level numeric constants.

var f = 0x1.ffffp+10   // (1 + ffff/2^16) × 2^10
var g = 0x1p-2         // 1 × 2^-2 = 0.25

In the first example, 1.ffff is the hex mantissa and p+10 multiplies by 2^10. The integer part or fractional part can be omitted, but the exponent part is mandatory.

Hexadecimal floats require the exponent:

Leaving out the p exponent in a hex float literal is a syntax error. 0x1.5 is illegal; you need 0x1.5p0 or similar.

Underscores in floats

Just like integer literals, floating‑point literals may contain underscores between digits for readability.

pi := 3.141_592_653_589_793
mask := 0x1.ffff_0000p+0

Common mistakes

  • Writing 0x1.5 — the compiler sees hex prefix and a dot, then reaches 5 and expects a p exponent. It will not silently treat it as a decimal float.
  • Using e exponent in a hex float (e.g. 0x1p10e2) — the exponent part must be p/P only; e means nothing after the hex part.
  • Omitting both integer and fractional parts and relying only on the exponent: e10 is invalid because there is no mantissa to scale. You need at least 1e10 or .1e10.

When you see a ‘p’ exponent, think powers of two:

If you’re writing constants that must be exact binary fractions — for instance, a minimum step in a DSP routine — hex floats let you express them with zero conversion error.

Imaginary Literals

Go has built‑in support for complex numbers. An imaginary literal is an integer or floating‑point literal followed by the letter i. The i acts as a suffix that creates an imaginary constant. When used with a complex64 or complex128 type, the literal represents the coefficient of the imaginary unit.

x := 1i        // imaginary integer, equivalent to 0 + 1i
y := 2.5i      // imaginary float
z := 0x1p-2i   // imaginary hex float

The literal 1i on its own is an untyped complex constant. It can be assigned to a complex128 variable or used in arithmetic with other complex values.

i is not a suffix for ordinary integers:

You cannot write 5i without a real part and expect it to be a regular integer — 5i is always imaginary. If you need the integer 5, just write 5.

How imaginary literals work with complex numbers

A full complex literal is typically built by adding a real part to an imaginary literal:

c := 3 + 4i

The compiler sees the addition of a real constant 3 and an imaginary constant 4i and produces a complex constant. You can also use the complex built‑in function, but literals are more direct.

Beginners sometimes think i is a variable — it’s not. It’s part of the literal syntax, so i on its own is not defined unless you import math and use math.Sqrt(-1), which is not a constant.

‘i’ must immediately follow the number:

A space between the number and i breaks the literal: 2 i is not valid syntax. The compiler will treat 2 as an integer and then see a bare i identifier, which will likely cause an “undefined: i” error.

Rune Literals

A rune literal represents a single Unicode code point. It is written between single quotes, like 'a'. In Go, the type rune is an alias for int32, so a rune literal is actually an integer constant.

Basic characters and escape sequences

You can write a rune literal as a single Unicode character directly:

ch := 'A'
japanese := '世'

Rune literals support the same escape sequences as interpreted string literals, plus a few extras specific to single characters:

  • \a – alert (bell)
  • \b – backspace
  • \f – form feed
  • \n – newline
  • \r – carriage return
  • \t – tab
  • \v – vertical tab
  • \\ – backslash
  • \' – single quote
  • \" – double quote (legal in a rune, though rarely needed)
  • \x followed by exactly two hex digits for a byte value
  • \u followed by exactly four hex digits for a 16‑bit Unicode code point
  • \U followed by exactly eight hex digits for a 32‑bit Unicode code point
tab     := '\t'
newline := '\n'
heart   := '\u2665'   // ♥
smiley  := '\U0001F600' // 😀 (code point beyond Basic Multilingual Plane)

The \x form is byte‑sized, not a full code point. '\x41' equals 'A'. The \u and \U forms express the code point directly, so '\U0001F600' is a valid rune even though its value exceeds 0xFFFF.

Rune literal value is an integer

Because rune is int32, you can use a rune literal anywhere an integer constant is expected. The following prints the numeric code of the heart emoji:

fmt.Println('\u2665')  // prints 9829

This fact often surprises beginners who expect a rune to behave like a one‑character string. A rune literal is not a string — it’s a number. If you want a string, use double quotes.

Single quotes ≠ strings:

Wrapping a string in single quotes is a compile‑time error: 'hello' will not parse. Only a single Unicode character (or an escape that expands to one code point) can appear inside single quotes.

Invalid rune literals

The following are common invalid attempts:

c1 := ''        // empty rune literal is illegal
c2 := '\xZ'     // invalid hex digit after \x
c3 := '\u12'    // too few hex digits after \u (needs 4)
c4 := '\U12'    // too few hex digits after \U (needs 8)
c5 := '\n''     // unescaped single quote breaks the literal (use '\'' instead)

Correct multi‑line rune escaping:

If you need a rune that represents a single quote, write '\''. The backslash turns the inner quote into a literal character rather than the end of the rune.

String Literals

String literals are sequences of characters enclosed in either double quotes " (interpreted) or backticks ` (raw). They produce values of type string, which is a read‑only slice of bytes holding the UTF‑8 encoding of the content.

Interpreted string literals

An interpreted string literal is written between double quotes and may contain escape sequences. The compiler replaces each escape with the corresponding byte value. Line breaks are not allowed inside interpreted strings — you must use the \n escape instead.

s1 := "Hello, 世界"
s2 := "Line one\nLine two"
s3 := "She said \"Hi\""
s4 := "Null byte: \x00"

The following escape sequences are available in interpreted strings (the same as rune escapes except those specific to single‑character representation like \a and \b which are also allowed):

  • \n, \r, \t, \\, \"
  • \xNN (two hex digits, byte value)
  • \uNNNN (four hex digits, Unicode code point)
  • \UNNNNNNNN (eight hex digits, Unicode code point)

The \x, \u, and \U escapes produce the UTF‑8 encoding of the specified byte or code point in the resulting string.

Raw string literals

A raw string literal is enclosed in backquotes and contains the exact text between the quotes — no escape processing occurs. Newlines, tabs, and backslashes are all preserved as‑is.

raw := `This is a raw string.
It spans multiple lines
and "quotes" and \backslashes\ are literal.
`

Raw strings are especially useful for regular expressions, JSON templates, and embedded SQL because you don’t need to double‑escape backslashes.

re := regexp.MustCompile(`\d+\.\d+`)   // easy to read
// vs interpreted: "\\d+\\.\\d+"         // backslash overload

A raw string literal cannot contain a backtick, because the first backtick ends the string. If you must embed a backtick, you concatenate with an interpreted string:

withBacktick := `Here is a backtick: ` + "`" + ` inside raw.`

Newlines in interpreted strings are illegal:

You might be tempted to break a long string across lines in your source code. That works in raw strings, but an interpreted string with an actual newline before the closing " will fail to compile. Use \n or + concatenation instead.

Backslash‑only escapes

A backslash followed by a character that is not a recognised escape is a compile‑time error. For example, "\q" is invalid. This catches typos early.

Invalid escape error:

If you see an error like unknown escape or illegal character escape, check that you’ve spelled the escape correctly. Common culprit: "\e" which doesn’t exist in Go — use "\x1b" for the ESC character if you need it.

Default choice: interpreted unless escaping hurts readability:

For most short strings with occasional escapes, interpreted strings are the natural choice. Switch to raw strings the moment you find yourself adding several backslash‑heavy escape sequences.


Summary

Integer, floating‑point, imaginary, rune, and string literals share one goal: they let you express constant values directly in code, with rules that make the code unambiguous to both the compiler and other developers. The base prefixes for integers, the mandatory p exponent for hex floats, the i suffix for imaginary numbers, the single‑quote‑for‑runes rule, and the two string forms each remove guesswork — you know exactly what value you’re creating just by looking at the syntax.

The most important skill to build now is reading a literal and immediately recognising its type and possible pitfalls. When you see 0123, the leading zero means octal; when you see 0x1.5, check for the p exponent; when you see 'a', remember it’s a number. With those reflexes, the rest of Go’s type system and constant rules will feel predictable rather than surprising.