← Back to blog
Data Formats·August 19, 2026·6 min read

YAML's Whitespace Footguns: Why the Norway Problem and Tab Indentation Keep Breaking Configs

YAML's indentation-as-syntax and implicit type inference make it readable to write and dangerous to get subtly wrong — here's why 'NO' becomes false, why tabs are banned, and why 1.10 silently becomes 1.1.

A Kubernetes manifest that deploys fine in staging and silently skips a container in production. A CI pipeline step that "runs" but does nothing, because a key got demoted from a mapping to a scalar. A config value that's the string "12345" in one file and the integer 12345 in another, and a downstream parser that only handles one of them. None of these are YAML bugs in the sense of a parser doing the wrong thing. They're YAML doing exactly what the spec says, using whitespace rules most people never read.

YAML's pitch was always "JSON, but for humans" — no braces, no trailing commas, no quote-everything ceremony. The cost of that readability is that structure lives in invisible characters: how many spaces indent a line, whether a scalar got auto-typed, whether a block ended where you thought it did. This is a tour of the specific whitespace and typing rules that keep causing outages, and why the spec is built this way in the first place.

Indentation is the syntax, not a style choice

In JSON, whitespace is decoration. {"a":1} and { "a" : 1 } parse identically. In YAML's block style, indentation is the delimiter — it plays the role that {, }, and , play in JSON. Two mappings at different indent levels are different nesting depths, full stop.

service:
  name: api
  ports:
    - 8080
  env:
    - NODE_ENV=production

Here ports and env are both children of service because they share the same indentation (2 spaces). Shift env: one space to the right and it silently becomes a child of ports instead of a sibling — and because ports was expecting a sequence, not a mapping, some parsers will throw, but others will just produce a structure you didn't intend and don't notice until something downstream reads a missing key.

The rule that trips people up hardest: tabs are not allowed for indentation, anywhere, in block context. This isn't a style-guide preference, it's in the YAML 1.1 spec and carried into 1.2 — tabs are reserved as separators in other contexts and mixing them into indentation makes the "how deep is this line" calculation ambiguous across editors with different tab-width settings. Most parsers reject a literal tab character used for indentation outright, which is actually the better failure mode — a loud parse error beats a document that indents differently depending on whether your editor renders tabs as 2, 4, or 8 columns.

The Norway problem: implicit typing

YAML doesn't require quotes around scalars, which means every unquoted value gets run through a type-resolution step that guesses whether it's a string, number, boolean, or null. YAML 1.1 — which is what most real-world parsers (PyYAML's default loader, older Ruby Psych, many Go libraries) actually implement — has an unusually generous idea of what counts as a boolean:

Unquoted valueYAML 1.1 resolves to
no, No, NOfalse
yes, Yes, YEStrue
on, On, ONtrue
off, Off, OFFfalse
null, ~, emptynull
NO (as in Norway's ISO 3166 country code)false

That last row is the famous one — the "Norway problem." A YAML file listing country codes:

countries:
  - NO
  - SE
  - FR

produces [false, "SE", "FR"] under a YAML-1.1-compliant loader, because NO matches the boolean pattern before it's considered a plain string. This has broken real config files and CI pipelines, not as a hypothetical — it's referenced directly in the YAML 1.2 Core Schema rationale for why 1.2 narrowed the boolean set down to just true/false. The catch: your parser's default schema determines which spec version's rules apply, and plenty of production systems still run 1.1-schema parsers, so "we upgraded YAML" in your head doesn't mean the loader agrees.

The fix that always works, regardless of schema version, is to quote anything that isn't unambiguously the type you want: "NO", "yes", "on". Quoting a string in YAML costs nothing and removes it from type inference entirely.

Numbers get the same treatment, differently

Version fields are the classic casualty:

version: 1.10

A human reads 1.10 as "one point ten." A YAML float parser reads it as 1.1 — the trailing zero after a decimal point isn't semantically significant to a numeric type, so it's silently dropped in the in-memory representation, and depending on how the consumer serializes it back out, 1.10 and 1.1 can end up indistinguishable. Docker Compose files, Helm charts, and Kubernetes API versions all hit this; the standard workaround is the same as above — quote it: version: "1.10".

Octal and sexagesimal (base-60!) number formats are another 1.1-vs-1.2 landmine. Under YAML 1.1, 010 parses as octal 8, and colon-separated numbers like 1:30:00 parse as a base-60 integer (5400). YAML 1.2's core schema drops both of these in favor of a stricter, JSON-compatible number grammar. If your config has a leading-zero string that's meant to stay a string — a zip code, a phone extension, a version segment — quote it, always, on every parser, on every YAML version.

Flow style collapses the whole problem — sometimes

YAML also supports "flow style," which is just JSON syntax embedded inside a YAML document:

ports: [8080, 8443]
env: {NODE_ENV: production, LOG_LEVEL: debug}

Flow style sidesteps indentation-as-structure entirely — braces and brackets delimit scope explicitly, same as JSON, so the Norway problem's typing rules still apply (an unquoted on inside flow style is still a boolean) but indentation bugs can't happen inside a flow collection. This is why tools that generate YAML programmatically (Helm's --set, kubectl output, most YAML libraries' default dump behavior for nested structures) often fall back to flow style for anything past a certain nesting depth — it's less pretty but structurally unambiguous. If you're hand-writing something with deep nesting and keep getting indentation wrong, flow style for the deepest levels is a legitimate escape hatch, not a hack.

Multi-line strings: three ways to be wrong

YAML has two block scalar indicators for literal multi-line text — | (literal, preserves newlines as-written) and > (folded, converts single newlines to spaces) — each modifiable with - (strip trailing newline) or + (keep all trailing newlines). Getting these confused is a routine source of bugs in anything that embeds a script or certificate in YAML (CI configs, Kubernetes ConfigMaps):

literal: |
  line one
  line two
folded: >
  line one
  line two

literal produces "line one\nline two\n". folded produces "line one line two\n" — the newline between the two source lines becomes a space, which is exactly what you want for a wrapped paragraph and exactly what you don't want for a shell script or PEM certificate, where > will silently join lines that needed to stay separate. The failure mode here is quiet: the YAML parses fine, the string comes out some value, and it's only wrong once something downstream tries to execute or verify it.

Why the spec is built this way

None of this is an accident or an oversight — it's the direct cost of YAML's founding goal of being a strict superset-adjacent, human-editable format that still maps cleanly onto native data structures in Perl, Python, and Ruby (YAML's original target languages, circa 2001). JSON made the opposite trade: mandatory quotes and braces buy unambiguous parsing at the cost of hand-editing comfort. TOML, which showed up later specifically as a reaction to YAML's implicit-typing complexity, makes a third trade — explicit types like JSON, but a flatter, more restrictive structure than YAML's arbitrary nesting, which is why it's become the default for tool configs (Cargo, pyproject.toml) where the schema is shallow and known in advance, while YAML keeps its grip on deeply nested, human-authored infrastructure config (Kubernetes, CI pipelines, Ansible) where the nesting is the point.

If you write YAML professionally, the practical checklist is short: never use tabs for indentation, quote anything that isn't obviously the type you want (especially country codes, version numbers, and yes/no-shaped strings), know whether your parser defaults to 1.1 or 1.2 core-schema typing, and pick | vs > deliberately rather than by habit. Running a document through a YAML validator before it hits a pipeline catches the indentation and syntax class of bug immediately — it won't stop the Norway problem, since NO is valid YAML that just resolves to a type you didn't want, but it will catch the malformed-structure failures before they reach production.

#yaml#data-formats#kubernetes#devops#yaml-parsing#configuration

Related reading

Data Formats
JSON Schema in Practice: What allOf, oneOf, and additionalProperties Actually Do
Data Formats
Base64 vs Base32 vs Base58: Why Bitcoin Doesn't Use the Encoding Your API Does
Data Formats
NDJSON vs JSON Arrays for Streaming: Why the Trailing Bracket Is the Problem