Gleam String Builder
The gleam/string_builder module (also available as gleam/string_tree in newer versions) builds large strings efficiently by deferring concatenation. When you join strings with <> repeatedly, each join allocates a new string. A string builder accumulates pieces and concatenates them all at once at the end — significantly faster for large outputs.
The Problem With Repeated Concatenation
Naive concatenation — expensive:
──────────────────────────────────────────────────
let result = "header" <> "\n"
let result = result <> "line 1" <> "\n"
let result = result <> "line 2" <> "\n"
let result = result <> "line 3" <> "\n"
let result = result <> "footer"
Each <> creates a new string in memory.
For 1000 lines: ~1000 allocations.
With string builder — efficient:
──────────────────────────────────────────────────
All pieces stored as a tree.
One final allocation when converting to String.
Creating a String Builder
import gleam/string_builder as sb
// Start from a string
let builder = sb.from_string("Hello")
// Start empty
let empty = sb.new()
Appending and Prepending
import gleam/string_builder as sb
let result =
sb.new()
|> sb.append("First line\n")
|> sb.append("Second line\n")
|> sb.append("Third line\n")
|> sb.to_string
// "First line\nSecond line\nThird line\n"
String Builder Tree Structure
──────────────────────────────────────────────────
append("First\n") → [node: "First\n"]
append("Second\n") → [node: "First\n", node: "Second\n"]
append("Third\n") → [node: "First\n", node: "Second\n", node: "Third\n"]
to_string() → joins all nodes in one pass
→ "First\nSecond\nThird\n"
Joining a List of Strings
import gleam/string_builder as sb
import gleam/list
let words = ["Gleam", "is", "fast", "and", "safe"]
let sentence =
words
|> list.map(sb.from_string)
|> sb.join(" ") // joins with spaces
|> sb.to_string
// "Gleam is fast and safe"
Building HTML Efficiently
import gleam/string_builder as sb
pub fn render_list(items: List(String)) -> String {
let rows =
items
|> list.map(fn(item) {
sb.from_strings(["", item, " "])
})
|> sb.join("\n")
sb.new()
|> sb.append("\n")
|> sb.append_builder(rows)
|> sb.append("\n
")
|> sb.to_string
}
render_list(["Apple", "Banana", "Cherry"])
──────────────────────────────────────────────────
- Apple
- Banana
- Cherry
String Builder vs String Concatenation
Performance Comparison
──────────────────────────────────────────────────
Items │ Concatenation (<>) │ String Builder
──────┼─────────────────────┼──────────────────
10 │ Fast (no diff) │ Fast
100 │ Noticeable │ Fast
1000 │ Slow │ Fast
10000 │ Very slow │ Fast
Rule of thumb:
< 20 strings → use <> directly
≥ 20 strings → use string builder
Common String Builder Functions
string_builder Module Reference
──────────────────────────────────────────────────
sb.new() → empty builder
sb.from_string(s) → builder from a String
sb.from_strings([s1, s2]) → builder from list of Strings
sb.append(builder, s) → add String to end
sb.prepend(builder, s) → add String to start
sb.append_builder(b1, b2) → combine two builders
sb.join(builders, sep) → join list of builders with separator
sb.to_string(builder) → convert to final String
sb.byte_size(builder) → byte length without converting
sb.is_empty(builder) → check if empty
sb.split(builder, sep) → split on separator
sb.reverse(builder) → reverse content
sb.lowercase(builder) → all lowercase
sb.uppercase(builder) → all uppercase
Practical Example — CSV Generator
import gleam/string_builder as sb
import gleam/list
import gleam/int
type Row {
Row(name: String, age: Int, city: String)
}
pub fn to_csv(rows: List(Row)) -> String {
let header = sb.from_string("name,age,city\n")
let body =
rows
|> list.map(fn(row) {
sb.from_strings([
row.name, ",",
int.to_string(row.age), ",",
row.city
])
})
|> sb.join("\n")
sb.new()
|> sb.append_builder(header)
|> sb.append_builder(body)
|> sb.to_string
}
pub fn main() {
let data = [
Row("Alice", 30, "Delhi"),
Row("Bob", 25, "Mumbai"),
Row("Carol", 35, "Bangalore")
]
io.println(to_csv(data))
}
// Output:
// name,age,city
// Alice,30,Delhi
// Bob,25,Mumbai
// Carol,35,Bangalore
String Tree (Newer API)
In recent versions of gleam_stdlib, the module is called gleam/string_tree and the type is StringTree. Both string_builder and string_tree work the same way — the API is identical:
import gleam/string_tree as st
let tree =
st.new()
|> st.append("Hello, ")
|> st.append("World!")
|> st.to_string
// "Hello, World!"
Key Points
String Builder Essentials
──────────────────────────────────────────────────
1. Use when building strings from many pieces (20+)
2. sb.new() or sb.from_string() to start
3. sb.append() and sb.prepend() add content
4. sb.join() combines a list with a separator
5. sb.to_string() finalizes — one allocation
6. Immutable — every operation returns a new builder
7. gleam/string_tree is the newer name for the same API
8. No performance difference for small strings — use <> then
String builders turn an O(n²) problem into an O(n) one. For generating reports, HTML, CSV, JSON, or any template where you combine many pieces of text, a string builder produces the result faster and with less memory pressure than repeated <> concatenation.
