A small punctuation rule in JSON reveals something larger about how language design shapes the daily friction of editing code, and why some separators help while others fight you.
Most arguments about syntax feel like aesthetic squabbles, the programming equivalent of debating whether the toilet paper roll goes over or under. But buried inside a recent piece from the Computer Things newsletter, written by Hillel Wayne of Logic for Programmers fame, is a genuinely sharp observation about why one tiny rule in JSON causes a disproportionate amount of friction. The rule is simple: a comma may separate two members of an object, but it may not trail the last one. The consequences are anything but simple.

The thesis hiding in a comma
The central claim is that separators which appear only between elements, and never at the edges, force editing operations to depend on position. That dependency is the real cost. When every line in a list looks structurally identical, you can add, delete, or reorder any of them with the same mechanical edit. The moment the first or last element becomes a special case, you have introduced a branch into what should be a uniform operation.
Consider what happens when you append a key to a JSON object whose final member has no trailing comma. You cannot simply add a line. You must first reach back to the previous line, append a comma there, and only then write your new entry. The edit you wanted to make has quietly become two edits, one of which touches a line you had no intention of changing. Version control feels this immediately. A one-line addition shows up in the diff as a two-line change, and the noise compounds across a codebase.
This is the core argument, and it generalizes well beyond JSON. The question a language designer is implicitly answering with separator placement is not "what looks clean" but "which edits stay local."
How the problem spreads across languages
Wayne walks through a catalog of offenders, and the variety is instructive because it shows the problem is not specific to data formats. Haskell record syntax famously pushes the comma to the front of each field, producing the leading-separator or "partial bullet point" style. That choice inverts the JSON pain rather than removing it. The last field becomes trivial to edit, but the first field is now the special case, because it is the one without a leading comma.
TLA+ carries the same tension into specification work, where the cost is felt acutely because you spend your time constantly adding and removing top-level variables. The irony Wayne points out is that PlusCal, the algorithm DSL layered on top of TLA+, sidesteps the issue entirely by using a different separator convention. Two dialects of the same ecosystem disagree about whether your edits should be uniform.
The extreme case is Prolog, which not only lacks trailing separators but introduces a distinct terminating symbol, the period, that differs from the comma used between goals. You can almost reframe the period as a strange closing brace, but the language does not bless that interpretation, and anyone reading your code will not thank you for the cleverness. The point is that a clause body and a clause boundary are marked by different characters, so the visual rhythm of a rule breaks precisely where editing is most frequent.
What the better languages actually do
Go and Python land on the pragmatic middle ground. Both permit a trailing comma, which means appending to the end of a collection is a clean single-line edit. This is why Go's tooling can be so aggressive about formatting; the grammar was built to make uniform edits possible. You can see the philosophy reflected in the Go specification and in Python's own grammar reference. Neither, however, allows a leading comma, so the first element remains a mild special case and full bullet-point styling stays off the table.
The most permissive design Wayne surfaces belongs to Alloy, the modeling language, which accepts both leading and trailing commas. With both edges relaxed, every line genuinely becomes interchangeable, and the editing model finally matches the intuition that a list is an unordered bag of similar things. Alloy then wanders into excess by permitting empty separators, so a block stuffed with redundant commas still parses. Wayne calls this "stuttering" and admits he cannot find a way to weaponize it, which is a refreshingly honest note in a piece otherwise built on careful reasoning. Permissiveness has a tail, and the tail is usually nonsense that a parser will dutifully accept.
The counter-argument deserves its hearing
The case against trailing separators is not frivolous, and the article gives it real space. The objection is ambiguity. In Prolog, the period that ends a clause is load-bearing: replace it with a comma and a separate fact like bar(c) could be misread as an additional goal inside the preceding rule. The terminator is doing semantic work, distinguishing where one definition stops and the next begins. Ruby exposes a parallel hazard with method chaining, where a trailing call separator would blur the line between a method on the receiver and a fresh top-level function call.
Wayne draws the right distinction here, and it is the most important conceptual move in the piece. These ambiguities arise around control separators, the punctuation that sequences statements or chains operations, not data separators, the punctuation that lists the elements of a structure. Data lists have a natural closing delimiter, the brace or bracket, that already tells the parser where the collection ends. A trailing comma inside that boundary cannot be confused for anything, because the closing brace resolves the ambiguity. Control flow has no such enclosing marker, so the separator itself must carry the boundary information. The lesson is that trailing separators are safe exactly where a delimiter already brackets the construct, and dangerous where it does not.
Python supplies the one genuine edge case on the data side, and it is a consequence of overloaded syntax rather than a flaw in the trailing-comma idea. Because parentheses serve both grouping and tuple construction, the trailing comma becomes the only thing distinguishing the integer (2+3) from the single-element tuple (2+3,). Here the trailing comma is not redundant decoration, it is the entire semantic signal. That is a fair reminder that punctuation rules interact, and you cannot reason about one separator in isolation from the rest of the grammar.
Why a punctuation rule is worth this much thought
The deeper pattern is that syntax is an interface for editing, not only for reading. Every grammar encodes an implicit theory of which changes are cheap and which are expensive. JSON's prohibition on trailing commas, inherited from a desire to keep the format minimal and unambiguous, optimized for a parser's simplicity at the expense of an editor's. JSON5 and JSONC, the informal supersets used in configuration files, exist largely to undo this one decision, which tells you how much friction it generated in practice.
The broader insight connects to a recurring theme in language design: local reasoning is the property that scales. A diff that stays small, an edit that does not ripple, a reordering that touches only the lines you meant to touch. These are the same virtues we praise in module boundaries and pure functions, expressed at the smallest possible granularity, the space between two elements of a list. When Wayne says nontrailing separators do not spark joy, the joy he is describing is the absence of incidental work, the feeling of an edit that does exactly what you intended and nothing more. That a single comma can carry that much weight is a useful reminder that no part of a notation is too small to design with care.

Comments
Please log in or register to join the discussion