Looking back at V8’s August 2025 post on JSON.stringify performance is a useful exercise, because the headline number (more than 2x faster) understates what had to change to get there. JSON.stringify is one of those functions that looks simple from the outside and turns out to be a collection of interconnected hard problems once you try to make it fast.
Why This Function Is Hard to Optimize
The API surface of JSON.stringify is small: pass a value, optionally a replacer and a space argument, get a string back. The implementation is nothing like that. The ECMAScript specification’s definition spans dozens of abstract operations covering recursive descent serialization, prototype chain property enumeration, toJSON method dispatch, replacer function invocation, circular reference detection, and special handling for undefined, functions, and symbols.
Each of those features is a branch in the fast path. Any object could have a toJSON method. Any value could be a Symbol wrapper. Any key in an object could be a getter that triggers arbitrary user code. A conformant implementation has to check for all of these, which means a naive fast path is not actually valid unless you can prove none of those conditions hold.
The result is that JSON serialization in V8 has historically been organized around a slow path that handles the full spec and a set of increasingly narrow fast paths that bypass checks only when safety can be established. The 2025 work expanded and deepened those fast paths significantly.
String Scanning: The Obvious Bottleneck
String serialization is the most common operation in any real JSON payload. Keys are strings. Most values in typical API responses and localStorage data are strings. Serializing a string means wrapping it in double quotes and escaping any characters that require it: ", \, and all control characters below 0x20.
The previous implementation scanned strings one character at a time, consulting a lookup table to decide whether each character needed escaping. This is correct and straightforward. It is also scalar, which means it serializes eight characters in the same time a SIMD-capable loop could process 16 or 32.
V8 has two internal string representations: one-byte strings (Latin-1, where each character fits in a byte) and two-byte strings (UC16, used for content containing characters outside Latin-1). The overwhelming majority of JSON keys and typical string values are ASCII and therefore stored as one-byte strings. Optimizing the one-byte string path with wider comparisons, or better branch prediction around the escape check, is where a large portion of the speedup comes from.
The key insight is that most strings in real JSON data contain no characters requiring escaping at all. If you can prove that upfront by scanning the whole string and finding nothing, you can write the opening quote, copy the entire string in one memcpy-equivalent operation, and write the closing quote. The question is how fast you can scan. Using SIMD intrinsics or compiler auto-vectorization on the character scan loop, you can check 16 bytes per iteration rather than one, making the “is this string clean” test cheap enough that the fast copy path pays off broadly.
Object Serialization and Shape Information
For objects, the cost of JSON.stringify is dominated by property enumeration. The spec requires iterating enumerable own string-keyed properties in creation order, which maps onto V8’s internal property system based on hidden classes (also called Maps or shapes).
For objects that have a stable shape, V8 can theoretically use that shape information to precompute which properties to enumerate and in what order, avoiding repeated descriptor array lookups. The challenge is that this caching has to be invalidated any time the object’s shape changes, and it has to stay consistent with the spec even when properties are deleted or redefined.
The fast path for object serialization also has to check whether any property has a toJSON method, whether the property is actually a data property (rather than an accessor), and whether the value at each property requires the slow path. These checks are unavoidable but can be sequenced to fail early when the common case holds.
The userland comparison: fast-json-stringify
To understand what the V8 improvement means, it helps to look at fast-json-stringify, a library maintained by the Fastify project. Its approach is completely different: you provide a JSON Schema describing the structure of your data, and the library generates a specialized serialization function at startup time.
Because the generated function knows the exact set of keys, their types, and their order, it can skip all the property enumeration and type-checking that the built-in has to do speculatively. For well-structured API payloads, fast-json-stringify has historically been 2 to 5 times faster than the built-in on V8.
That gap exists because the built-in is a general-purpose implementation and the schema-based approach is a specialized one. The V8 optimization doesn’t close that gap fully, but it narrows it. For applications that cannot adopt a schema-based workflow, or for code paths where the serialized shape is not statically known, the improved built-in is the only option.
This is also where the 2x headline becomes interesting. A 2x improvement on the built-in moves it from roughly the cost of two or three fast-json-stringify calls to roughly the cost of one or two. That’s a meaningful change in when reaching for a library is worth the added schema maintenance cost.
Number and Boolean Serialization
Numbers and booleans are simpler cases but still appear frequently. V8’s number-to-string conversion is already highly optimized using the Grisu3 and Dragon4 algorithms for floating-point formatting, so the gains here are smaller. For integers specifically, integer detection and a faster decimal formatting path avoid the floating-point machinery entirely.
Booleans are just the string literals "true" and "false", so the main optimization is making sure the branch gets predicted correctly and the string copy is cheap.
Array Serialization
Arrays are another high-frequency case. V8 has multiple internal array representations based on element kind: SMI-only arrays (small integers), double arrays, and general object arrays. A JSON serializer aware of these kinds can take specialized paths for each. An array of SMIs doesn’t need toJSON checks per element; it just needs number-to-string formatting. An array of doubles needs isFinite checks to comply with the spec (since Infinity and NaN serialize to null in JSON), but those checks are cheap on hardware that has IEEE 754 status flags available.
The main cost for arrays is the per-element comma insertion and the length check in the loop. These are hard to eliminate but can be restructured to reduce branch mispredictions.
What This Means in Practice
The functions most affected by the improvement are the ones that dominate production workloads: serializing moderately large objects with string values, serializing arrays of records coming from a database query, and repeatedly serializing objects that have stable shapes.
In Node.js HTTP servers, JSON.stringify sits on the critical path for every response that sends structured data. In Next.js and other SSR frameworks, it runs during page serialization to send initial state to the client. In Electron applications, it crosses the main-renderer process boundary as part of IPC. In React applications using Redux, it runs on every state snapshot if any middleware serializes state for debugging or persistence.
For workloads where the previous 10ms was JSON serialization, 5ms is a meaningful reduction in perceived latency. For server applications handling tens of thousands of requests per second, the CPU time saved per request compounds.
The Broader Pattern
V8’s history of JSON.stringify optimization reflects a general pattern in JavaScript engine development: built-ins start as correct, general-purpose implementations, accumulate real-world performance data, and then get narrowed with fast paths as the team identifies which cases dominate in practice. The 2025 work continues that progression.
It is worth noting that the JSONT proposal and related tc39 discussions around richer JSON tooling have been raising questions about what the built-in serialization API should be able to do. Faster execution of the current API is useful today, but there is a parallel conversation about whether JSON.stringify needs to be extended or replaced to handle BigInt, arbitrary types, and streaming.
For now, the improvement ships in V8 and propagates into Chrome and Node.js. If you are running a version of Node.js built on the post-August 2025 V8, you are getting this for free. No code changes required, no schema to maintain. The function that handles the output of every REST API you’ve ever written just got faster.