Xo is experimental. The source is now public on GitHub, or try Xo in your browser.
Menu · Xo and Go

Docs

Xo and Go

On this page

The first page of a comparison series (also Xo and Rust, Xo and TypeScript, and Xo and Python). It is not a “which is better” page: Xo started as a language compiled through Go, it keeps much of Go’s design, and Go is the more mature tool for most work today. This page says how the two relate, where each one is the better choice, and what the measurements show, including the rows where Xo is slower.

Versions. Claims about Go are checked against Go’s own documentation as of Go 1.27.3 (the newest release, 2026-10-19). The measurements below were taken against Go 1.26.1, the Go installed on the measuring machine; they will be rerun against 1.27 at the next release. Xo figures are from xo 0.32.0 unless a row says otherwise. Last verified: 2026-10-25.

Short version

Choose Go when you need a stable, widely known language with a compatibility promise in force today, a large ecosystem, many targets, fast incremental builds, mature debugger and profiler support on every platform, or a team that already knows it. Go is production proven at very large scale; Xo is experimental (spec draft 0.10, no 1.0 yet).

Consider Xo when you want the compiler to reject more mistakes before a program runs (unhandled errors, nil, missing match cases, data races, undeclared side effects), when you want a dependency’s capabilities limited and recorded, and when much of the code is written by AI agents that work from structured compiler feedback. Xo can still use any Go package through its Go backend.

What each is for

  • Go: “a language for writing server programs that would be easy to maintain over time”, with a goal of “removing obstacles to working on large code bases” (Go FAQ).
  • Xo: backends, tools, and web services, designed so AI agents can write, check, cross compile, and debug programs with fast, structured feedback (Why Xo?). It keeps Go’s small keyword set, single formatter, static binaries, and strong standard library.

The language

GoXo
Absent valuesnil is the zero value of pointers, slices, maps, channels, functions, and interfaces; assigning to a nil map panics (spec)No nil; T? is Option[T], and the compiler makes you handle None (core 2.5)
ErrorsMultiple results with an error value; ignoring it compiles. The Go team stopped pursuing new error syntax in 2025 (blog)T ! E (Result); an unhandled Result does not compile; ? propagates and records an error trace (core 2.6, 11.1)
Sum typesNone: “they overlap in confusing ways with interfaces” (FAQ)Enums with payloads and exhaustive match, with guards (core 2, 3)
GenericsType parameters since Go 1.18 (release notes)Generics with constraints (core 2.7)
ValuesPointers and shared mutable slices and mapsValue semantics with copy-on-write; mutation is visible in signatures (var parameters) (core 5)
ConcurrencyGoroutines and channels; cancellation through context.ContextStructured concurrency: tasks live inside a scope, cancellation flows down without a ctx parameter, channels and select (core 7)
Side effectsNot in signaturesEffects in signatures (uses (net, fs)), capability values passed from main, and fakes for tests (core 6)

Memory

  • Go uses a concurrent, non-moving, tracing mark-sweep garbage collector, tuned by GOGC and the soft limit GOMEMLIMIT (GC guide). Cycles are collected.
  • Xo native builds (the default) use reference counting: a value is freed when its last reference goes, with no collector pauses. Value semantics leave no cycles to collect. HTTP handlers get request scoped arenas by default. --backend=go uses Go’s collector instead (FAQ, core 10).
  • Reference counting has a cost Go does not pay: counts go up and down as values move. xo vet --cost points at the loops where that, or a copy-on-write copy, is expensive.

Safety and security

GoXo
Memory safetySafe without data races. With them, Go’s memory model says races on interfaces, maps, slices, and strings “can in turn lead to arbitrary memory corruption” (memory model)Values never alias mutably, so data races are impossible except through Mutex and Atomic, which are synchronized (core 5)
Finding races-race (ThreadSanitizer) finds races that happen at run time, at “5-10x” memory and “2-20x” time for a typical program (race detector)Prevented by the type system; xo build --race (ThreadSanitizer) is there too (decision 0273)
Integer overflowWraps; “Overflow does not cause a run-time panic” (spec)Faults: a bug report, not a wrong number (core 2, 8); wrapping is explicit (wrapping_add, std 2)
CapabilitiesAny package can open files, sockets, or processesCode reaches the file system, network, environment, or processes only through capability values it was given; a module can be held to a ceiling (uses none) (core 6, Dependencies)
Supply chaingo.sum hashes each module version, and the checksum database sum.golang.org is a transparent log that keeps proxies and moved tags from serving different code (modules reference); govulncheck reports known vulnerabilities your code reaches (docs)xo.lock hashes each module version, a checksum log and proxies (core 4.2, 4.3), plus an effect lock: an update that adds an effect (a dependency that starts using the network) fails until accepted, and xo audit lists each dependency’s effects, escape hatches, and vouches (Dependencies). There is no vulnerability database yet

Compiler and toolchain

Go 1.26.1xo 0.32.0
Cold build of a small program1.45 to 1.50 s1.09 to 1.29 s
Warm build, nothing changed0.03 to 0.04 s0.03 to 0.04 s
Build after a one-function edit0.10 to 0.11 s0.33 to 0.36 s
Peak build memory (cold)0.31 GB0.49 to 0.52 GB
Binary size (same programs, plain builds)2.39 to 2.43 MB1.72 to 1.79 MB
Binary size, CRUD server, shipping flags (measured with xo 0.29.0, 2026-10-24)5.48 MB (go build -trimpath -ldflags="-s -w"; 8.03 MB without the flags)2.28 MB (xo build --release; 6.23 MB without the flag)

Source: release-vs-go-v0.32.0.md, lines 9 to 33. The shipping-flags row: twins-build-2026-10-24.md, lines 39 to 42. Xo builds a small program cold faster than Go, and an unchanged warm build now matches Go’s (0.27 to 0.34 s in 0.27.0, 0.03 to 0.04 s now). Go rebuilds faster after an edit (about a third of Xo’s time) and uses less memory to build.

For tests the picture is mixed: on five std modules a cold xo test build took 2.8 to 5.8 s against go test’s 2.4 to 8.3 s, with far less CPU, because Go compiles each imported std package from source while xo links a prebuilt runtime; warm, Xo was 2 to 7 times slower, and after an edit 1.7 to 2.2 times slower on the native backend (test-speed-vs-go-2026-10-21.md, lines 74 to 86).

  • Cross compiling. Go: set GOOS and GOARCH; “you are always cross-compiling”, across 43 valid pairs (install docs). Xo: xo build --targets=all builds linux/amd64, linux/arm64, darwin/amd64, darwin/arm64, windows/amd64, and wasip1/wasm from any host with its own backends, no C toolchain; windows/arm64 and every other Go target through --backend=go (core 11).
  • Debug info. Go writes compressed DWARF into the binary on every platform (gdb docs), which Delve reads; on macOS, Apple’s own tools read none of it by default, and on Windows there is no PDB. Xo embeds uncompressed DWARF in the executable on macOS and Linux, writes a .dSYM on request, and writes a PDB on Windows that WinDbg and Visual Studio read. The details, with every tool’s output: Where macOS keeps a program’s debug info.
  • Tests and fuzzing. Go has go test and, since 1.18, native fuzzing on amd64 and arm64 (fuzzing docs). Xo has test blocks, property tests, deterministic concurrency tests (xo test --explore=N runs N schedules), and contract fuzzing: xo test fuzzes every function with requires/ensures contracts, and a shrunk failure becomes a permanent test (core 9). Contract fuzzing runs on the Go backend only today.
  • Formatting. Both have one formatter (gofmt, xo fmt); xo build --ci fails on an unformatted file or a leftover dbg(...).

Deployment and runtime

  • Both produce static binaries.
  • Memory while running: on the twenty workloads of the 0.32.0 comparison, Xo’s peak memory was 0.38 to 0.97 of Go’s (release-vs-go-v0.32.0.md, lines 34 to 295). A CRUD server under 1,024 connections peaked at 47 to 60 MB against Go’s 66 to 78 MB (net-vs-go-2026-10-22.md, line 33).
  • Startup: not measured as a comparison yet; it is on the list for the next release’s run.
  • Platforms: Go runs on many more operating systems and architectures. Xo’s native backends cover darwin, linux, and windows on arm64 and x86-64, plus WASI.

Writing it with AI agents

Go is the better known language: models have seen far more Go than Xo, and Go’s tooling (gopls, go vet, staticcheck) is mature. Xo is designed for the agent’s loop:

  • xo check --json reports every problem with a stable XOnnnn code and machine-applicable fixes; codes never change meaning (diagnostics).
  • Habits from other languages (nil, ?:, x as T, **, Python comprehensions) get a diagnostic that names the language and the Xo form, with a fix.
  • xo def, xo refs, xo query, xo edit, and xo mcp answer semantic questions and make edits through resolved symbols.
  • In a study with two independent authors who each planted 30 safety bugs, Xo prevented or caught 23 to 29, against 13 for Go with strict analyzers and 9 for plain Go. Agents passed their own tests on the first try about as often in both, and Xo programs were about half the size; but Go needed no spec in the agent’s context, while Xo needed about 30k tokens of it per session (three-arm-2026-10-08.md, lines 34 to 47).

Benchmarks

Method. The 0.32.0 comparison builds the same programs twice, once in Go and once in Xo (bench/perf’s Go twins: compute, collections, concurrency), and runs each workload under xo bench --ab with Go as A and Xo as B, 10 interleaved rounds; a verdict is “better” or “worse” only when the 95% interval excludes 1.0. Machine: Apple M4 Pro (12 cores), 24 GB, macOS 27, darwin/arm64, with other jobs running (load 3 to 4 at the start of each run). Versions: the released xo 0.32.0 archive and Go 1.26.1. Date: 2026-10-25. Ratios are Xo over Go; under 1.0 means Xo took less. (release-vs-go-v0.32.0.md, lines 1 to 8 and 34 to 44)

workloadwall time, Xo / Goverdict
collections: branch0.024better
collections: iter0.25better
concurrency: mutex0.31better
interpreter0.41better
strings0.45better
concurrency: spawn0.47better
collections: putget, get, copymut, update, push0.59 to 0.72better
concurrency: fanout0.73better
sort0.81better
collatz0.96better
fib, sieve, rwmutex, ping-pong0.91 to 1.19within noise
concurrency: parallel map1.08worse
n-body (floating point)1.42worse

Each row’s raw output, with spreads and intervals: release-vs-go-v0.32.0.md, lines 34 to 295. In 0.27.0 rwmutex was 3.58 times Go’s time and spawn 1.20. The rwmutex row is lock bound: one task at a time can make progress, and Go’s goroutines stay with the waking P while Xo spread the 64 tasks over all its workers at once, so they contended; since 0.29 a task whose loop only takes a Mutex or calls Atomic methods starts its spawn site small (decision 0363), and a contended RWMutex takes 0.26 of its 0.28 time, which closed the gap (rwmutex-vs-go-2026-10-23.md, lines 13 to 16 and 50 to 55).

Standard library, instructions retired (fewer is better; Apple M4 Pro, Go 1.26.1, 2026-10-21; native Xo against Go’s packages):

workloadXo / Gosource
JSON decode (small documents)0.33json-vs-go, line 45
JSON encode (small documents)0.43json-vs-go, line 46
multipart parse1.15json-vs-go, line 50
regexp match7.55regexp-time-csv-vs-go, line 29
regexp find_all2.83regexp-time-csv-vs-go, line 30
time format10.06regexp-time-csv-vs-go, line 33
time parse10.37regexp-time-csv-vs-go, line 34
time civil fields0.41regexp-time-csv-vs-go, line 35
csv parse1.67regexp-time-csv-vs-go, line 37

Network (native Xo against Go’s net/http, crypto/tls, and gorilla/websocket, 2026-10-23; Xo’s rate over Go’s, higher is better for Xo). The Mac was not quiet: load 5.0 to 5.7 at the start of each run, rising to 23 during the CRUD rounds. Reps are interleaved (Xo, Go, Xo, Go) so a load burst hits both alike; the table gives the median and the number of reps, and the file gives every rep. TLS, HTTP/2, WebSocket, and HTTP/1.1 rows run natively on macOS; the CRUD row runs in linux/arm64 Docker on the same Mac (net-vs-go-2026-10-23.md, lines 7 to 30).

workloadXo / Go (median)repssource
HTTP/2, 100 streams2.13line 40
HTTP/2, 1 stream1.223line 39
HTTP/2, 256 MiB download1.355line 36
TLS 1.3 bulk, AES-128-GCM1.374line 37
TLS 1.2 bulk, ChaCha20-Poly13051.175line 38
TLS 1.3 handshakes (x25519, P-256, resumed)1.04 to 1.463lines 41 to 43
WebSocket echo, 64 B, 1 connection0.975line 54
WebSocket echo, 64 B, 16 connections1.205line 55
CRUD server, 1,024 connections (Docker)1.06 requests/s; 0.87 of Go’s CPU per request5lines 66 to 67
HTTP/1.1 server rows (plain, item, items, echo, router)1.08 to 1.43 requests/s; 0.39 to 0.63 of Go’s CPU per request3lines 81 to 87

The single-connection WebSocket row is within the spread of its reps (0.86 to 1.46). An earlier run on 2026-10-22 under heavier load showed gaps on the bulk TLS and download rows (0.61 to 0.76, net-vs-go-2026-10-22.md); the re-measure with interleaved reps did not reproduce them.

These are a young compiler’s numbers on one machine; the slower rows are the work queue. Every release reruns the comparison (bench/results/release-vs-go-vX.Y.Z.md) and this page’s tables are refreshed from it.

↑ index