countrycode

countrycode provides ISO-3166 country metadata compiled into the binary, with lookup by alpha-2, alpha-3 and numeric code and selection by geographic hierarchy.

Part of nurago, a collection of independent Go packages for backend services.

import "github.com/tecnickcom/nurago/pkg/countrycode"

ISO-3166 country records, compiled in, queryable by code or by geography.

d, err := countrycode.New(nil)
c, err := d.CountryByAlpha2Code("IT")

What a record holds

Each CountryData carries all three ISO-3166 code forms (alpha-2, alpha-3, numeric), English and French names, the full UN M49 geographic hierarchy (region, sub-region, intermediate region, each with both name and code), an assignment status, and the country-code top-level domain.

The three-code redundancy is the point. Systems disagree about which form they store: payment processors tend to alpha-2, some government and banking formats use alpha-3, statistical datasets use numeric. Having all three on one record makes the package a translation table, not just a validator.

The French names are there because ISO-3166 is a bilingual standard and publishes both as normative.

Assignment status is not a formality

CountryData records a status, and codes have several.

The embedded dataset carries five: Officially assigned, User-assigned, Exceptionally reserved, Transitionally reserved, and Indeterminately reserved.

This matters for validation. XK for Kosovo is user-assigned rather than official but appears throughout real data. GB is the official code for the United Kingdom while UK is only exceptionally reserved, and plenty of systems send UK. Treating the alpha-2 space as a flat set of valid or invalid values gets both wrong.

CountriesByStatusName and CountriesByStatusID select on it, so a validator can accept the set your system actually needs rather than the set that happens to be embedded.

Geographic queries

Beyond single lookups, the package selects sets:

d.CountriesByRegionCode("150")             // Europe, by UN M49 code
d.CountriesBySubRegionName("Western Asia")
d.CountriesByIntermediateRegionName("Caribbean")

Each level has both a code-based and a name-based form. The EnumRegion, EnumSubRegion, EnumIntermediateRegion and EnumStatus methods return the full code-to-name maps, enough to render a filter UI without hardcoding the hierarchy.

Intermediate region is the level people forget exists. It is how M49 distinguishes the Caribbean from South America inside the Americas, and there is no way to derive it from region and sub-region alone.

Costs

Operationns/opB/opallocs/op
New236,602114,223785
CountryByAlpha2Code1112272
CountryByAlpha3Code1332272
CountryByNumericCode1402272
CountriesByRegionCode5,92212,003103

Measured with go test -bench on Go 1.27.1, linux/amd64, Intel Core i7-1260P. Run go test -bench=. ./pkg/countrycode/ to reproduce them on your own hardware; the absolute values will differ, the ratios much less so.

Construction indexes the dataset three ways and costs 237 microseconds once. Single lookups then run at roughly 110 to 140 nanoseconds, allocating a copy of the record so the caller cannot mutate the shared dataset.

Set queries cost about 50 times a single lookup and allocate per result, since each one builds a fresh slice. Calling CountriesByRegionCode inside a per-request loop is the shape to avoid; call it once and keep the slice.

Build one *Data at startup and share it. It is read-only after construction.

The uint64 behind the index

Internally each country is encoded as a Reversible Numeric Composite Key: one uint64 holding the codes and hierarchy positions in bit fields, decodable back to the record without a lookup table. That is what keeps the index compact and the per-lookup allocation at two.

The encoding is unexported, so it is not part of the API you call. Packing an Entire Country Record into a Single uint64 walks through how the fields are laid out and why the key is reversible.

Replacing the dataset

The embedded defaults are drawn from ISO-3166, the CIA World Factbook, UN M49, and Wikipedia. New also accepts a []*CountryData of your own, and returns an error when the dataset fails to index.

Country data moves, slowly but it moves: codes get reassigned, names change, territories change status. A compiled-in dataset is a snapshot of the release you pinned. Passing your own covers a newer snapshot, an internal override where your systems use a non-standard code, or a curated subset that keeps the index small.

Example

d, err := countrycode.New(nil)
if err != nil {
    log.Fatal(err)
}

c, err := d.CountryByAlpha2Code("IT")

fmt.Println(c.NameEnglish, err)

Full source is in example_countrycode_test.go. More runnable examples are on pkg.go.dev.

Dependencies

This package reaches no external module: it uses only the Go standard library.