The Norway Problem and YAML Boolean Surprises Explained
Why the country code NO becomes False in YAML 1.1, how keys collide, which parsers are affected and three fixes: quote, use a 1.2 parser or a schema.

A developer loads a list of country codes from a YAML file, ships it, and a month later a customer in Norway reports that the country dropdown is missing an option. The list had NO in it, and the parser read it as the boolean false. This is the Norway problem, and it is the most famous example of a class of surprises: YAML decides the type of an unquoted word by what it looks like, and several common words look like booleans. This post explains the rule, shows the damage with real output and gives the fixes.
The rule behind it
YAML 1.1, the version that PyYAML and a long list of older tools implement, defines the boolean type with a generous set of spellings. The values y, yes, true, on and their capitalizations are true, and n, no, false, off and their capitalizations are false. A plain, unquoted scalar that matches is a boolean. The designers meant it to make configuration read like English (debug: on). The cost is that any word in that set is unusable as a string without quotes, and some of them are real data: NO is Norway, ON is Ontario, Y and N are letters, off is a menu position.
PyYAML implements yes, no, true, false, on and off in any of the usual cases, not the single letters. The test:
countries: [GB, FR, NO, SE, ON, OFF, Y, N]PyYAML : {'countries': ['GB', 'FR', False, 'SE', True, False, 'Y', 'N']}NO became False, ON became True and OFF became False. Y and N stayed strings, even though the specification lists them, because PyYAML chose not to resolve them. Whether a given library resolves the single letters differs between libraries, which is one more reason not to depend on it.
The 1.2 answer
YAML 1.2, released in 2009, narrowed the boolean type to true and false (in a few capitalizations) and removed yes, no, on, off. A parser that follows 1.2 loads the same document untouched:
ruamel : {'countries': ['GB', 'FR', 'NO', 'SE', 'ON', 'OFF', 'Y', 'N']}ruamel.yaml in safe mode implements 1.2 by default, and all eight codes come back as strings. yq (the Go implementation) behaves the same way; asked for the tag of the third element it answered !!str, and converting the document to JSON gave ["GB","FR","NO","SE"]. The catch is that you do not control which parser reads your file. Your application may use PyYAML, a deployment tool may use Go, and a CI service may use Ruby, and the same file means different things to each (YAML 1.1 versus 1.2 explains the differences and the libraries on each side).
Keys and collisions
The problem is not limited to values. Mapping keys are scalars too, so they are resolved the same way:
flags:
no: ok
on: 1
yes: 2PyYAML : 'flags': {False: 'ok', True: 2}The key no became False. The keys on and yes both became True, so they collided and the later entry, 2, overwrote the earlier, 1. Three keys went in and two came out, with no error. The 1.2 parser returned all three keys as strings. This is a realistic accident in files such as feature-flag lists, locale tables or CI step names (the key on: in GitHub Actions workflows is a well-known case, where the tool knows to treat it as the string).
The damage then travels. Dumping the PyYAML result to JSON gives:
{"countries": ["GB", "FR", false, "SE", true, false, "Y", "N"], "flags": {"false": "ok", "true": 2}}The keys became the strings "false" and "true", because JSON keys must be strings, and the values are real booleans. Anything that reads that JSON now sees a country list with booleans in it and a flags object with nonsense keys.
Three fixes, and when each works
Quote the values. "NO", "ON". Quoting makes the scalar a string in every version and every parser:
quoted : {'countries': ['NO', 'ON']}This is the fix that works everywhere, including in tools you do not control. Its weakness is people: a new row added without quotes breaks the data again. Pair it with a linter rule (yamllint's truthy rule flags unquoted yes, no, on and off, see linting YAML with yamllint) and a test that checks the list.
Use a 1.2 parser. If you control the reader, switch to ruamel.yaml or another 1.2 implementation. This fixes the whole class for files you write in 1.2 style. It does not protect files that other tools read with 1.1 rules, and it changes other behaviors at the same time, such as octal numbers (why a number turns into something else in YAML).
Validate types. Declare the field as a string in a schema, and fail loudly when the loaded value is a boolean. With pydantic, a field typed str rejected NO read as False with "Input should be a valid string" and showed the offending input, False (validating YAML configuration with pydantic). The same check can run in JSON Schema. This catches the problem on the day it is introduced instead of the day a customer finds it.
For data that is really a table of codes, consider whether YAML is the right container. A CSV of country codes has the same hazard when a spreadsheet touches it, and a JSON array of strings has none, since JSON requires quotes.
Related reading
The cluster hub is YAML syntax you need to stop guessing at. The wider family of surprises is in why a number turns into something else in YAML and YAML strings, quoting and when you need quotes. The boolean type is defined in the YAML 1.1 type repository and the narrower 1.2 rule in the core schema of the 1.2.2 specification. See how a file loads in the YAML to JSON tool.
When quoting will not fix the boolean problem
Another tool rewrites the file. A formatter or a library that drops quotes it considers unnecessary will remove yours when it follows 1.2 rules, and the next reader on 1.1 gets a boolean. Use round-trip tools that keep quote style.
The reader is not yours. A third-party service that parses your file with a 1.1 library will convert yes in places you cannot quote, such as keys it reads before your code. Test with their parser, or avoid the words.
The value is generated. If a program writes the YAML, it must quote strings that look like booleans. Good libraries do; template-based generators usually do not.
The type is decided later. A pipeline that reads YAML, writes CSV and reads the CSV in a spreadsheet can reintroduce the problem at every stage. Keep a schema at each boundary.


