A JSON document with 10 levels of nesting parses about the same speed as a flat document of equal size. A document with 1000 levels of nesting may not parse at all — many parsers have recursion limits that reject deeply nested input as a security measure. Between those extremes, nesting affects parser performance, memory layout, and consumer ergonomics in ways worth understanding.
Why parsers limit nesting depth
Most JSON parsers are implemented recursively: parsing a value calls parse-object or parse-array, which calls parse-value for each child, and so on. Each level of nesting consumes a stack frame. If an attacker sends a deeply nested document, the parser blows the stack and the process crashes.
To prevent this, parsers cap the depth. Python's json module has no built-in limit (you can crash it with a million open braces), but most production parsers do:
- Node.js V8: ~10,000 (varies by version and stack size)
- Go's
encoding/json: 10,000 (hardcoded) - Rust's
serde_json: 128 by default; configurable - Java's Jackson: 1,000 by default; configurable
For real-world JSON, even 100 levels of nesting is extraordinarily deep. If you find yourself needing more, your data model probably has a problem.
Performance: parsing time
For documents under, say, 50 levels deep, nesting depth barely affects parsing speed. The bottleneck is the total number of tokens (keys, values, punctuation), not how they're arranged. A flat object with 10,000 keys parses in roughly the same time as a 10-level-deep tree with 10,000 leaf values.
Where nesting matters is access patterns after parsing. Reaching a value at data.a.b.c.d.e.f requires six pointer dereferences. For a single read, the cost is negligible. For a loop reading the same deep path a million times, the dereferences add up; copying the value to a local variable first is the usual fix.
Performance: memory layout
Each nested object is a separate allocation in most languages (Python dict, Go map, Java HashMap, JavaScript object). Twenty nested objects with one key each cost more memory than one object with 20 keys, because each carries its own header, hash table, and pointer overhead.
For Python specifically, a dict's overhead is ~200-280 bytes before keys and values. A flat dict with 100 entries: ~5-10KB. A 100-level-deep nested structure with one key each: ~25KB. The deep version is 3-5× larger in memory despite holding the same data.
Performance: cache locality
Flat structures have better cache locality. When iterating over a flat array of objects, the objects' fields tend to be near each other in memory; the CPU's cache prefetcher can stream them in. With deep nesting, each level of dereference may cause a cache miss as you chase pointers.
For most applications this is in the noise. For tight inner loops over large datasets, it can be a 2-3× speedup to flatten.
Consumer code costs
The biggest practical cost of deep nesting is not parser performance — it's the code that has to navigate the structure. Consider:
// Deep
const city = response.data.user.profile.address.location.city;
// Flat
const city = response.user_city;
The deep version requires every level to exist. If any is null or undefined, you get a runtime error. Optional chaining (?.) helps but adds noise. The flat version is trivial to test, easier to mock, and survives schema changes better.
When nesting is worth it
Three legitimate reasons to nest:
The nested object is a real entity. An order contains an array of line items; each item is an object with product, quantity, and price. Flattening this into line_item_1_product, line_item_1_quantity, line_item_2_product... is much worse than the array of objects.
Reusable sub-structures. If "address" appears in multiple contexts (shipping, billing, home, work), nesting it consistently lets consumers write one piece of code to handle addresses everywhere.
Polymorphism. Different kinds of events might share envelope metadata but differ in payload. Nesting the payload under a key keyed by event type keeps the envelope clean.
When nesting is a smell
Avoid nesting when:
- The inner object always has exactly one field.
{"user": {"name": "Alice"}}should usually be{"user_name": "Alice"}. - The nesting reflects database joins rather than logical structure. APIs are not databases; consumers don't care about your schema.
- Levels are unnamed wrappers.
{"data": {"data": {"data": ...}}}is a code smell screaming for a flatten. - The same data could be represented as a flat list.
{"first": {"second": {"third": ...}}}chained linearly is just a list with extra steps.
Flattening responsibly
If you're flattening an existing structure, three patterns help:
Path-as-key: "address.city" as a literal key. Easy to generate; awkward to consume because the dot is a separator only by convention.
Underscored: "address_city". Easier for consumers because the key is a regular identifier in most languages.
Selective flattening: Keep one level of nesting where it's meaningful (an "address" object); flatten deeper levels into the address. This usually reads best.
For more on JSON design, see our JSON best practices guide and JSON in REST APIs.
Tooling
Our JSON formatter handles documents up to ~5MB and ~1000 levels of nesting in the browser. Beyond that, you'll want to flatten or stream. For practical handling of large JSON documents, see handling large JSON without running out of memory.