Polyglot Primer

Chapter 3: Strings and Text

Strings are immutable in all four languages, but what a "character" is differs sharply: bytes (Go), UTF-16 units (Java, JS) or code points (Python).

What a string is

GoJavaJavaScriptPython
TypestringStringstringstr
Mutable?NoNoNoNo
Internal encodingUTF-8 bytesUTF-16UTF-16Unicode code points (flexible storage)
len measuresbytesUTF-16 units (length())UTF-16 units (.length)code points (len(s))
Indexing s[i]a bytes.charAt(i) (a char)one-unit stringone-code-point string
Quote styles"...", ` raw `"...", """text block"""'..', "..", ` template `'..', "..", '''..''', r"raw"

Test with "héllo" (5 characters, é is 2 bytes in UTF-8): Go len gives 6; Java, JS and Python give 5. With an emoji like "😀": Go 4, Java 2, JS 2, Python 1.

Literals, multi-line and raw strings

s := "tab\there"
raw := `C:\path\no\escapes
and multi-line`
String s = "tab\there";
String block = """
    Hello
      indented text block (Java 15+)
    """;
const s = "tab\there";
const multi = `line one
line two`;                     // template literal: multi-line allowed
const raw = String.raw`C:\path`;
s = "tab\there"
multi = """line one
line two"""
raw = r"C:\path\no\escapes"

Interpolation and formatting

name, n := "Ada", 3
msg := fmt.Sprintf("%s has %d items (%.2f%%)", name, n, 12.5)
fmt.Println("plain", name, n)   // Println adds spaces between operands
String msg = String.format("%s has %d items (%.2f%%)", name, n, 12.5);
String msg2 = "%s has %d items".formatted(name, n);   // Java 15+
// String templates (STR."...") are still preview features: don't rely on them.
const msg = `${name} has ${n} items (${(12.5).toFixed(2)}%)`;
msg = f"{name} has {n} items ({12.5:.2f}%)"      # f-string (3.6+)
msg2 = "{} has {} items".format(name, n)         # older style

Interpolation is first-class in JS and Python; Go and Java use printf-style verbs. Go verbs worth knowing: %v (any value), %+v (struct with field names), %#v (Go syntax), %T (type), %q (quoted), %x (hex).

Concatenation

GoJavaJavaScriptPython
Operatora + ba + ba + ba + b
Mixed with numberserrorconverts the numberconverts the numberTypeError
Many pieces in a loopstrings.BuilderStringBuilder+= is fine (engine optimises), or array.join"".join(parts)

Why builders: each += on an immutable string copies it, making loops O(n^2). Java and Go need an explicit builder; Python's "".join(list) is the idiom.

Common operations

TaskGo (strings)Java (String)JavaScriptPython
Lengthlen(s)s.length()s.lengthlen(s)
Containsstrings.Contains(s, x)s.contains(x)s.includes(x)x in s
Starts withstrings.HasPrefix(s, x)s.startsWith(x)s.startsWith(x)s.startswith(x)
Ends withstrings.HasSuffix(s, x)s.endsWith(x)s.endsWith(x)s.endswith(x)
Index ofstrings.Index(s, x) (-1 if none)s.indexOf(x) (-1)s.indexOf(x) (-1)s.find(x) (-1) or s.index(x) (raises)
Substrings[1:4] (bytes)s.substring(1, 4)s.slice(1, 4)s[1:4]
Upper / lowerstrings.ToUpper(s)s.toUpperCase()s.toUpperCase()s.upper()
Trim spacesstrings.TrimSpace(s)s.strip() (or trim())s.trim()s.strip()
Splitstrings.Split(s, ",")s.split(",") (regex!)s.split(",")s.split(",")
Joinstrings.Join(parts, ",")String.join(",", parts)parts.join(",")",".join(parts)
Replace allstrings.ReplaceAll(s, a, b)s.replace(a, b)s.replaceAll(a, b)s.replace(a, b)
Repeatstrings.Repeat(s, 3)s.repeat(3)s.repeat(3)s * 3
Reverseloop over runesnew StringBuilder(s).reverse()[...s].reverse().join("")s[::-1]
Comparea == b, strings.Comparea.equals(b), compareToa === b, localeComparea == b, a < b
Empty/blanks == ""s.isEmpty(), s.isBlank()s === ""not s

Gotchas in the table:

Slicing

s := "hello"
s[1:3]     // "el"  (half-open: start inclusive, end exclusive)
s[:2]      // "he"
s[3:]      // "lo"
s = "hello"
s[1:3]     # "el"
s[-2:]     # "lo"   (negative counts from the end)
s[::2]     # "hlo"  (step)
s[::-1]    # "olleh"
"hello".slice(-2);      // "lo" (negative ok)
"hello".substring(1, 3) // "el" (no negatives, swaps args if reversed - prefer slice)
"hello".substring(1, 3);   // "el"; throws StringIndexOutOfBoundsException if out of range

Iterating characters

for i, r := range "héllo" {   // r is a rune; i is the BYTE offset of the rune
    fmt.Println(i, string(r))
}
for i := 0; i < len(s); i++ { /* s[i] is a byte - breaks on non-ASCII */ }
for (char c : s.toCharArray()) { }          // UTF-16 units
s.codePoints().forEach(cp -> { });          // real code points (IntStream)
for (const ch of "héllo😀") { }             // iterates by code point
"😀".length;                                 // 2, but [..."😀"].length is 1
for ch in "héllo😀":
    print(ch)                                # code points

Go's range over a string decodes UTF-8 for you. Indexing does not. This is the single most important Go string fact.

Bytes vs text

GoJavaJavaScriptPython
Byte sequence[]bytebyte[]Uint8Arraybytes (immutable), bytearray
String -> bytes[]byte(s)s.getBytes(StandardCharsets.UTF_8)new TextEncoder().encode(s)s.encode("utf-8")
Bytes -> stringstring(b)new String(b, UTF_8)new TextDecoder().decode(b)b.decode("utf-8")

Python 3 strictly separates str from bytes - mixing them is a TypeError. Go lets you convert freely (it's a copy). Always name the charset in Java; the default changed to UTF-8 only in Java 18.

Regular expressions (standard library)

re := regexp.MustCompile(`(\d+)-(\d+)`)       // RE2 syntax: no backreferences or lookahead
m := re.FindStringSubmatch("10-20")           // ["10-20" "10" "20"]
Pattern p = Pattern.compile("(\\d+)-(\\d+)");
Matcher m = p.matcher("10-20");
if (m.matches()) { m.group(1); }
const m = "10-20".match(/(\d+)-(\d+)/);       // regex literals are built in
const named = "10-20".match(/(?<a>\d+)-(?<b>\d+)/).groups.a;
import re
m = re.match(r"(\d+)-(\d+)", "10-20")
m.group(1)

Go's regex engine guarantees linear time and so omits backreferences and lookaround. Java, JS and Python support them (and can suffer catastrophic backtracking).

Quirks and gotchas

What is idiomatic

Checklist

Exercises

In each language, write a function wordFrequencies(text) that:

  1. Lowercases the text and splits it on whitespace and punctuation.
  2. Counts each word (use a map/dictionary - chapter 6 covers them; a simple version is fine for now).
  3. Prints the top 3 words with their counts.

Then write a second function isPalindrome(s) that ignores case and non-letters and is correct for "Été" (non-ASCII input). Compare how much work Go requires for this versus Python.

Stretch: print the byte length and character count of "héllo😀" in all four languages and explain each number.