Working with ISO Country and Region Codes

javascript
// Get country with all code formats
const response = await fetch("https://worlddataapi.com/v1/countries/US", {
	headers: { "X-API-Key": "YOUR_API_KEY" },
});
const data = await response.json();

console.log(data);
// {
//   "code": "US",           // Alpha-2
//   "code_alpha3": "USA",   // Alpha-3
//   "code_numeric": 840,    // Numeric
//   "name": "United States",
//   ...
// }
python
import requests

response = requests.get(
    "https://worlddataapi.com/v1/countries/US",
    headers={"X-API-Key": "YOUR_API_KEY"}
)
data = response.json()

print(data)
# {
#   "code": "US",           # Alpha-2
#   "code_alpha3": "USA",   # Alpha-3
#   "code_numeric": 840,    # Numeric
#   "name": "United States",
#   ...
# }
bash
curl -X GET "https://worlddataapi.com/v1/countries/US" \
  -H "X-API-Key: YOUR_API_KEY"

ISO 3166 is the international standard for country codes. Understanding when to use alpha-2 (US), alpha-3 (USA), or numeric (840) codes prevents data mismatches and makes your application interoperable with external systems.

The Challenge#

Country codes seem straightforward until you encounter real-world complexity:

  • External APIs use different formats. Stripe uses alpha-2 (US), SWIFT uses alpha-3 (USA), and payment systems often use numeric (840).

  • The ISO code for the United Kingdom is GB, not UK. This catches developers off guard.

  • Region codes follow different patterns per country: US-CA for California, but JP-13 for Tokyo.

  • Territories like Puerto Rico (PR) and Hong Kong (HK) have their own codes, separate from their parent countries.

This tutorial walks through the ISO 3166 standard, shows you when to use each format, and demonstrates how to convert between them using World Data API.

Prerequisites#

Before starting this tutorial, you need:

  • A World Data API key (free tier works for this tutorial)

  • Basic familiarity with REST APIs

  • A development environment with JavaScript, Python, or curl

The Three Code Formats#

ISO 3166-1 defines three representations for each country:

FormatExampleLengthUse Cases
Alpha-2US2 charsMost common. URLs, APIs, user-facing
Alpha-3USA3 charsBetter readability. Some financial systems
Numeric8403 digitsLanguage-independent. Payment systems

Alpha-2 Codes#

Two-letter codes like US, GB, JP. The most widely used format:

javascript
// Alpha-2 examples
const codes = {
	US: "United States",
	GB: "United Kingdom", // Not 'UK'
	JP: "Japan",
	DE: "Germany",
	CN: "China",
};

Use alpha-2 for:

  • API parameters and responses

  • URL paths (/api/countries/US/holidays)

  • User-facing dropdowns (with full names displayed)

  • Domain names (.us, .jp)

  • Most databases and internal storage

Watch out for:

  • GB is the ISO code for the United Kingdom, not UK

  • Some codes aren't intuitive: CH = Switzerland, ES = Spain

Alpha-3 Codes#

Three-letter codes like USA, GBR, JPN. More readable but less common:

javascript
// Alpha-3 examples
const alpha3 = {
	USA: "United States",
	GBR: "United Kingdom",
	JPN: "Japan",
	DEU: "Germany",
	CHN: "China",
};

Use alpha-3 for:

  • Financial systems (SWIFT, some banking APIs)

  • Olympic country codes (close alignment)

  • Systems where readability matters more than brevity

  • Interoperability with legacy systems using this format

Numeric Codes#

Three-digit codes like 840, 826, 392. Language-independent:

javascript
// Numeric examples
const numeric = {
	840: "United States",
	826: "United Kingdom",
	392: "Japan",
	276: "Germany",
	156: "China",
};

Use numeric for:

  • International payment systems (ISO 4217 currency codes use the same pattern)

  • Systems that must work across different scripts/languages

  • EDI (Electronic Data Interchange)

  • Legacy mainframe systems

Benefit: Works in systems without alphabet support. 840 is unambiguous regardless of whether the system supports Latin characters.

Converting Between Formats#

The API accepts any format and returns all three:

javascript
// All of these return the same country
await fetch("/v1/countries/JP"); // Alpha-2
await fetch("/v1/countries/JPN"); // Alpha-3
await fetch("/v1/countries/392"); // Numeric
await fetch("/v1/countries/japan"); // Name (case-insensitive)

// Response always includes all formats
// {
//   "code": "JP",
//   "code_alpha3": "JPN",
//   "code_numeric": 392,
//   "name": "Japan",
//   ...
// }

Building a Converter#

javascript
async function fetchCountryCodeMap() {
	const countries = [];
	let page = 1;
	let hasMore = true;

	while (hasMore) {
		const response = await fetch(
			`https://worlddataapi.com/v1/countries?page=${page}&per_page=100`,
			{ headers: { "X-API-Key": API_KEY } },
		);
		const data = await response.json();
		countries.push(...data.data);
		hasMore = data.pagination.page < data.pagination.total_pages;
		page++;
	}

	// Build lookup maps
	const byAlpha2 = new Map();
	const byAlpha3 = new Map();
	const byNumeric = new Map();
	const byName = new Map();

	for (const country of countries) {
		byAlpha2.set(country.code, country);
		byAlpha3.set(country.code_alpha3, country);
		byNumeric.set(country.code_numeric, country);
		byName.set(country.name.toLowerCase(), country);
	}

	return {
		byAlpha2,
		byAlpha3,
		byNumeric,
		byName,

		// Conversion helpers
		toAlpha2: (input) => {
			const country = resolveCountry(input);
			return country?.code;
		},

		toAlpha3: (input) => {
			const country = resolveCountry(input);
			return country?.code_alpha3;
		},

		toNumeric: (input) => {
			const country = resolveCountry(input);
			return country?.code_numeric;
		},
	};

	function resolveCountry(input) {
		if (typeof input === "number") {
			return byNumeric.get(input);
		}
		const str = input.toString();
		return (
			byAlpha2.get(str.toUpperCase()) ||
			byAlpha3.get(str.toUpperCase()) ||
			byNumeric.get(parseInt(str)) ||
			byName.get(str.toLowerCase())
		);
	}
}

// Usage
const codes = await fetchCountryCodeMap();

codes.toAlpha2("JPN"); // "JP"
codes.toAlpha2(392); // "JP"
codes.toAlpha2("japan"); // "JP"

codes.toAlpha3("JP"); // "JPN"
codes.toNumeric("Japan"); // 392

ISO 3166-2: Region Codes#

ISO 3166-2 extends the standard to administrative subdivisions: states, provinces, territories, prefectures, cantons, etc.

javascript
// Get a region by ISO 3166-2 code
const response = await fetch("https://worlddataapi.com/v1/regions/US-CA", {
	headers: { "X-API-Key": "YOUR_API_KEY" },
});
const data = await response.json();

console.log(data);
// {
//   "code": "US-CA",
//   "name": "California",
//   "country": "US",
//   "kind": "state",
//   "timezone": "America/Los_Angeles"
// }

Region Code Format#

ISO 3166-2 codes follow the pattern: {country}-{subdivision}

javascript
const regionCodes = {
	"US-CA": "California",
	"US-NY": "New York",
	"GB-ENG": "England",
	"GB-SCT": "Scotland",
	"DE-BY": "Bavaria",
	"JP-13": "Tokyo",
	"CA-ON": "Ontario",
	"AU-NSW": "New South Wales",
};

The subdivision part varies by country:

  • United States: 2-letter state codes (US-CA, US-TX)

  • Japan: 2-digit prefecture codes (JP-13, JP-27)

  • Germany: 2-letter state codes (DE-BY, DE-NW)

  • United Kingdom: 3-letter codes (GB-ENG, GB-SCT, GB-WLS)

Listing Regions#

javascript
// Get all regions for a country
async function getRegionsForCountry(country) {
	const regions = [];
	let page = 1;
	let hasMore = true;

	while (hasMore) {
		const response = await fetch(
			`https://worlddataapi.com/v1/regions?country=${country}&page=${page}&per_page=100`,
			{ headers: { "X-API-Key": API_KEY } },
		);
		const data = await response.json();
		regions.push(...data.data);
		hasMore = data.pagination.page < data.pagination.total_pages;
		page++;
	}

	return regions;
}

const japanPrefectures = await getRegionsForCountry("JP");
console.log(japanPrefectures.length); // 47

Subdivision Types#

Different countries use different terminology:

javascript
// The API preserves local administrative vocabulary
const subdivisionTypes = {
	US: "state",
	CA: "province",
	AU: "state",
	JP: "prefecture",
	DE: "state",
	CH: "canton",
	FR: "region",
	GB: "country", // England, Scotland, Wales, NI
	AE: "emirate",
};

// Filter regions by type
function getStatesByType(regions, kind) {
	return regions.filter((r) => r.kind === kind);
}

Validation#

javascript
function isValidAlpha2(code) {
	return (
		typeof code === "string" &&
		code.length === 2 &&
		/^[A-Z]{2}$/.test(code.toUpperCase())
	);
}

function isValidAlpha3(code) {
	return (
		typeof code === "string" &&
		code.length === 3 &&
		/^[A-Z]{3}$/.test(code.toUpperCase())
	);
}

function isValidNumeric(code) {
	const num = typeof code === "string" ? parseInt(code) : code;
	return Number.isInteger(num) && num >= 1 && num <= 999;
}

function isValidRegionCode(code) {
	// Format: XX-YYY where XX is country, YYY is 1-3 alphanumeric
	return typeof code === "string" && /^[A-Z]{2}-[A-Z0-9]{1,3}$/i.test(code);
}

// Validate against actual data
async function validateCountryCode(code, codeMap) {
	const country =
		codeMap.byAlpha2.get(code.toUpperCase()) ||
		codeMap.byAlpha3.get(code.toUpperCase()) ||
		codeMap.byNumeric.get(parseInt(code));

	return country !== undefined;
}

Database Schema#

Store country references consistently:

sql
-- Countries table
CREATE TABLE countries (
  alpha2 CHAR(2) PRIMARY KEY,       -- "US"
  alpha3 CHAR(3) UNIQUE NOT NULL,   -- "USA"
  numeric SMALLINT UNIQUE NOT NULL, -- 840
  name VARCHAR(100) NOT NULL
);

-- Use alpha-2 as foreign key (most compact)
CREATE TABLE users (
  id SERIAL PRIMARY KEY,
  country CHAR(2) REFERENCES countries(alpha2),
  region VARCHAR(6)  -- ISO 3166-2: "US-CA"
);

-- Index for lookups by any format
CREATE INDEX idx_countries_alpha3 ON countries(alpha3);
CREATE INDEX idx_countries_numeric ON countries(numeric);

TypeScript Types#

typescript
// ISO 3166-1 Alpha-2 (249 countries)
type CountryAlpha2 =
	| "AD"
	| "AE"
	| "AF"
	| "AG"
	| "AI"
	| "AL"
	| "AM"
	| "AO"
	| "AQ"
	| "AR"
	// ... all 249 codes
	| "US"
	| "UY"
	| "UZ"
	| "VA"
	| "VC"
	| "VE"
	| "VG"
	| "VI"
	| "VN"
	| "VU"
	| "WF"
	| "WS"
	| "YE"
	| "YT"
	| "ZA"
	| "ZM"
	| "ZW";

// ISO 3166-2 Region Code
type RegionCode = `${CountryAlpha2}-${string}`;

// Country object
interface Country {
	code: CountryAlpha2;
	code_alpha3: string;
	code_numeric: number;
	name: string;
	official_name: string;
	continent: string;
	currencies: string[];
	languages: string[];
	timezones: string[];
	neighbors: CountryAlpha2[];
}

interface Region {
	code: RegionCode;
	name: string;
	country: CountryAlpha2;
	kind: string;
	timezone: string;
}

Special Cases#

Territories and Dependencies#

ISO 3166-1 includes territories separately from their parent countries:

javascript
const territories = {
	PR: "Puerto Rico", // US territory
	GU: "Guam", // US territory
	VI: "Virgin Islands, U.S.", // US territory
	HK: "Hong Kong", // China SAR
	MO: "Macao", // China SAR
	GI: "Gibraltar", // UK territory
};

// These have their own codes despite not being sovereign states

Former Countries#

ISO 3166-3 tracks deleted codes:

javascript
const formerCodes = {
	SU: "Soviet Union", // Deleted 1992
	CS: "Czechoslovakia", // Deleted 1993
	YU: "Yugoslavia", // Deleted 2003
	AN: "Netherlands Antilles", // Deleted 2010
};

// Never reuse deleted codes for different countries

Reserved Codes#

Some codes are reserved and never assigned:

javascript
const reservedCodes = [
	"UK", // Reserved for United Kingdom (use GB instead)
	"EU", // Reserved for European Union
	"AA",
	"QM",
	"QN", // User-assigned ranges
	"XA",
	"XZ", // User-assigned ranges
	"ZZ", // Unknown territory
];

Building a Country/Region Picker#

jsx
function LocationPicker({ onSelect }) {
	const [countries, setCountries] = useState([]);
	const [regions, setRegions] = useState([]);
	const [selectedCountry, setSelectedCountry] = useState("");
	const [selectedRegion, setSelectedRegion] = useState("");

	useEffect(() => {
		// Load countries once
		fetchAllCountries().then(setCountries);
	}, []);

	useEffect(() => {
		if (!selectedCountry) {
			setRegions([]);
			return;
		}
		// Load regions when country changes
		getRegionsForCountry(selectedCountry).then(setRegions);
	}, [selectedCountry]);

	const handleCountryChange = (code) => {
		setSelectedCountry(code);
		setSelectedRegion("");
		onSelect({ country: code, region: null });
	};

	const handleRegionChange = (code) => {
		setSelectedRegion(code);
		onSelect({ country: selectedCountry, region: code });
	};

	return (
		<div className="location-picker">
			<select
				value={selectedCountry}
				onChange={(e) => handleCountryChange(e.target.value)}
			>
				<option value="">Select country...</option>
				{countries.map((c) => (
					<option key={c.code} value={c.code}>
						{c.name} ({c.code})
					</option>
				))}
			</select>

			{selectedCountry && regions.length > 0 && (
				<select
					value={selectedRegion}
					onChange={(e) => handleRegionChange(e.target.value)}
				>
					<option value="">
						Select {regions[0]?.kind || "region"}...
					</option>
					{regions.map((r) => (
						<option key={r.code} value={r.code}>
							{r.name}
						</option>
					))}
				</select>
			)}
		</div>
	);
}

Caching Recommendations#

Country and region codes rarely change. Cache aggressively:

javascript
// Countries: Cache for 30 days
// Regions: Cache for 30 days
// Changes happen maybe once per year (new country, territory status change)

const COUNTRY_CACHE_TTL = 30 * 24 * 60 * 60 * 1000; // 30 days

async function getCountries() {
	const cached = localStorage.getItem("countries");
	if (cached) {
		const { data, timestamp } = JSON.parse(cached);
		if (Date.now() - timestamp < COUNTRY_CACHE_TTL) {
			return data;
		}
	}

	const countries = await fetchAllCountries();
	localStorage.setItem(
		"countries",
		JSON.stringify({
			data: countries,
			timestamp: Date.now(),
		}),
	);

	return countries;
}

Common Pitfalls#

Pitfall 1: Using UK Instead of GB#

javascript
// Incorrect
const country = "UK";

// Correct
const country = "GB"; // ISO 3166-1 alpha-2 for United Kingdom

The code UK is explicitly reserved in ISO 3166-1 to avoid confusion, but GB (Great Britain) is the official code. However, note that Northern Ireland is part of the UK but not Great Britain—the standard is imperfect.

Pitfall 2: Assuming 2-Letter Codes Are Universal#

javascript
// Incorrect - not all external systems use alpha-2
const stripeCountry = "JP";
const swiftCountry = "JP"; // SWIFT actually uses alpha-3: JPN

// Correct - convert based on system requirements
const swiftCountry = codes.toAlpha3("JP"); // "JPN"

Pitfall 3: Hardcoding Region Codes#

javascript
// Incorrect - region codes aren't always intuitive
const regions = {
	california: "US-CAL", // Wrong! It's US-CA
	tokyo: "JP-TK", // Wrong! It's JP-13
};

// Correct - fetch from API or use a verified lookup
const california = await fetch("/v1/regions/US-CA");

Pitfall 4: Mixing Standards#

javascript
// Incorrect - mixing ISO with other standards
const data = {
	country: "USA", // Alpha-3
	region: "California", // Name, not ISO code
	currency: "USD", // ISO 4217 (different standard!)
};

// Correct - be consistent within your system
const data = {
	country: "US", // Alpha-2
	region: "US-CA", // ISO 3166-2
	currency: "USD", // ISO 4217 (related but separate)
};

Pitfall 5: Forgetting Territories Have Separate Codes#

javascript
// Incorrect - assuming territories use parent country code
const userLocation = "US"; // User is in Puerto Rico

// Correct - territories have their own codes
const userLocation = "PR"; // Puerto Rico has its own ISO code

Summary#

ISO 3166 provides three code formats for countries: alpha-2 (most common, use for APIs and databases), alpha-3 (financial systems and SWIFT), and numeric (payment systems and legacy integrations). ISO 3166-2 extends this to regions with the pattern {country}-{subdivision}.

Key takeaways:

  • Use alpha-2 (US) as your default—it's compact and widely supported

  • Convert to alpha-3 (USA) or numeric (840) when integrating with systems that require them

  • Remember GB is the ISO code for the United Kingdom, not UK

  • Region code formats vary by country—fetch from the API rather than hardcoding

  • Cache country and region data aggressively (30-day TTL is reasonable)

Ready to integrate ISO country codes? Get your free API key and start building with World Data API. The free tier includes 60 requests per day—enough to build and test your country selector.

Related tutorials: