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:

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:

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.