On the virtues of the trailing comma

Raymond Chen

Many programming languages allow trailing commas in lists.

C, C++, C# (and probably other languages) permit a trailing comma after the last enumerator:

enum Color
{
    Red,
    Blue,
    Green,
    //   ^ trailing comma
};

They also allow a trailing comma in list initializers.

// C, C++
Thing a[] = {
    { 1, 2 },
    { 3, 4 },
    { 5, 6 },
    //      ^ trailing comma
};

// C#
Thing[] a = new[] {
    new Thing {
        Name = "Bob",
        Id = 31415,
        //        ^ trailing comma
    },
    new Thing {
        Name = "Alice",
        Id = 2718,
        //       ^ trailing comma
    },
//   ^ trailing comma
};

Dictionary d = new Dictionary<string, Thing>() {
    ["Bob"] = new Thing("Bob") { Id = 31415 },
    ["Alice"] = new Thing("Alice", 2718),
    //                                  ^ trailing comma
};

These trailing commas are convenient when you arrange for each element to appear on its own line, like we did in the examples above. It lets you rearrange the items by moving lines around without having to worry about having to add a comma to an element when it moves out of the final position, or removing a comma from the element that moved into the final position.

It also reduces merge risk when people modify the list. For example, if somebody adds a new color “Black” to the end, they won’t have to touch any of the other lines, which means that a change from “Blue” to “LightBlue” won’t result in a merge conflict.

And even when there is a merge conflict due to two simultaneous adds, you can easily resolve it by accepting both.

enum Color
{
    Red,
    Blue,
    Green,
<<< VERSION 1
    Black,
|||
    White,
<<< VERSION 2
};

To resolve this, you can just delete all the conflict markers.

enum Color
{
    Red,
    Blue,
    Green,
    Black,
    White,
};

If your code didn’t use trailing commas, the merge would be messier:

enum Color
{
    Red,
    Blue,
<<< VERSION 1
    Green,
    Black
|||
    Green,
    White
<<< VERSION 2
};

And if you have a lot of these merges to deal with, you might forget to insert a comma after “Black”:

enum Color
{
    Red,
    Blue,
    Green,
    Black // ⇐ oops, forgot a comma
    White
};

Since the trailing comma reduces the number of lines of code that have to be modified when the list is extended, it also makes git blame more accurate. Without the trailing comma, a git blame on enum Color would blame the person who added “Black” for also being the last person to modify the “Green” line. If you’re investigating a problem with “Green”, you might ask that person for help, and they’ll say, “Oh no, I didn’t add ‘Green’. I added ‘Black’. You’ll have to dig further back into the history to figure out who added ‘Green’.”

C
Thank
C++
you
C#
for
Java
supporting
JSON
Not you
Java­Script
trailing
Rust
commas
Go
in
Python
lists

Bonus chatter: The trailing comma also makes it easier for code generators, since they can just emit a comma after each element and not have to worry about suppressing the final comma.

Bonus bonus chatter: But why not go all the way and allow a trailing comma in parameter lists?

SomeFunction(1, 2, );
//               ^ trailing comma not allowed

I suspect the primary reason is “nobody asked for it.” Variadic functions are relatively uncommon, so this is not something that code generators stumble across. Also, that extra comma just plain looks weird.

Overloaded functions could pose a parsing problem. If there are 2-parameter and 3-parameter overloads of Some­Function, is this a call to the two-parameter overload, or is it a call to the three-parameter overload with some sort of default?

Bonus bonus bonus chatter: JavaScript, Rust, and Ruby allow a trailing comma in parameter lists.

Bonus bonus bonus bonus chatter: In the Pascal programming language, the semicolon is a statement separator, not a statement terminator, so you can write

begin
  i := 1;
  j := 2  (* no trailing semicolon *)
end

In practice, everybody puts a semicolon just before the end. Imaging rearranging two lines of code and having to adjust semicolons.

25 comments

Discussion is closed. Login to edit/delete existing comments.

  • Gwynne Raskind 0

    Bonus bonus bonus bonus chatter: Swift also allows trailing commas.

  • Melissa P 0

    Bonus chatter: The trailing comma also makes it easier for code generators, since they can just emit a comma after each element and not have to worry about suppressing the final comma.

    yep, yep, yep, yep …

    or if you have code re-arrangers, that shuffle lines to adjust for heuristics/adaptive usage count etc, PGO if you will, that don’t have to bother with surrounding syntax. Could be used as an interview question: “what are your thoughts about trailing commas”, and reveals your true level of programming experience.

    • alan robinson 0

      But perhaps not in the way you thought – those of us raised on the first ANSI C (replacing K&R) didn’t have trailing commas, and I’ve never adjusted to their addition (esp. since I have to maintain some old C code with some frequency).

  • Yuri Khan 1

    The correct configuration for a tool marking up merge conflicts is to include the base version:

    
    enum Color
    {
        Red,
        Blue,
    <<< VERSION 1
        Green,
        Black
    ===
        Green
    |||
        Green,
        White
    <<< VERSION 2
    };
    

    Without that, conflicts of the kind “line changed one way here and another way there” are difficult to resolve.

    • John Price 0

      > Imaging rearranging two lines of code and having to adjust semicolons.

      Erlang has syntax that requires this sometimes. Expressions in the body of a function clause are separated by commas, with no comma on the last one. Function clauses are separated by semicolons, but the last one is terminated with a period. Shuffle anything around and you probably have to change punctuation on a bunch of lines.

      https://www.erlang.org/doc/reference_manual/functions

  • Dan Bugglin 0

    I will take your Pascal statement separators and raise you JavaScript’s.

    Not only are JavaScript’s ; separators and not terminators, they are optional!

    Example:

    var functionThatAlwaysReturnsTrue = function() { return // Wow this line is getting long!
    true; };

    This function always returns undefined with no errors or warnings. Yes, this happened to me in real code.

    So, this means if you separate two lines with a newline and both are valid expressions on their own, they will be treated as separate statements?

    Wrong.

    (function() {
    /* artificial closure */
    })()
    (function() {
    /* artificial closure */
    })()

    This produces an error. The first function is called, and then the second function is seen as an argument to a function call to the return value of the first function. The first function doesn’t return anything so it errors out.

    This is also from real code I’ve written. I left out the ; by accident.

    So basically JavaScript does whatever the hell it feels like if you forget to use ;s.

    It does support trailing commas though.

    • Csaba Varga 0

      The truth is weirder than that. Semicolons are statement terminators in Javascript and the grammar requires them, but there is a process called Automatic Semicolon Insertion (ASI for short) that tries replacing newlines with semicolons if it runs into a parsing error. They had some good intentions behind adding ASI to the language, to make it more friendly to beginners, but the rules are pretty arbitrary, so most professionals just try to avoid it altogether rather than trying to rely on it.

    • Dmitry 0

      While JS behaviour is really annoying in the cases mentioned, I guess it’s worth defending it a bit. Noone would really like to see «Parsing error» every now and then while loading a webpage with cats or pussies. IE-based CHM viewer is well-known for doing that (not cats and pussies), and that annoys even more.

      In fact, I feel this being close to the philosophy of C: yes, the piece of code compiles and runs but it contains undefined behaviour, so that’s not a piece of code written in C. Well, OK, not valid C.

      Definitely, we still can see error messages in browser console for really badly corrupted JS code, but the amount and effect on the usability are significantly lower.

  • John Perry 0

    I wish the extra trailing comma was allowed in SQL VALUES (. . .) lists and IN (. . .) lists.
    Although some developers/DBAs prefer to use leading commas in a list making it easier to comment out lines when debugging etc.
    Personally I dislike the SQL leading comma style.

    • 紅樓鍮 1

      It sounds similar to the Haskell style of formatting lists:

      [ "Red"
      , "Green"
      , "Black"
      ]

      It does have the same advantages as trailing commas when it comes to git blame etc, as long as you’re not adding items to the head of the list. Considering that adding to the head of a list is less common than adding to the end, it is OK (but still strictly worse than trailing commas).

    • Sigge Mannen 0

      Interestingly, create table syntax allows trailing commas (at least in sql server):

      create table tx(
      	i int
      ,	z float
      ,
      )
      
  • Swap Swap 2

    The worst consequence of not using a trailing comma is that you can make a mistake when merging two versions or rearranging the lines, and the compiler won’t warn you. For example:

    "Red",
    "Green"
    "Blue"

    Here, Green and Blue were added in two different branches. They will be concatenated together by the preprocessor, so the effective code will be:

    "Red",
    "GreenBlue"

    which is a bug.

  • Rohitab Batra 1

    JSON – Not you 🙂

  • Andy Janata 0

    Not only does Go support the trailing comma, it _requires_ it in the situations shown here.

  • GL 0

    In C++ you can use trailing commas in brace initialization, and the extra comma does not change how overload resolution works. In C# if you have a normal variadic (i.e., params array, not vaargs) method and you find yourself constantly changing the number of arguments or shuffling them, then it’s better to call it with an explicitly initialized array, for which you can use trailing comma.

  • 紅樓鍮 0

    And the chad F# allows you to write lists and argument lists with no commas at all!

    [
        "Red"
        "Blue"
        "Green"
    ]
    
    SomeFunction
        1
        2
  • Neil Rashbrook 0

    Note that Pascal doesn’t allow a semicolon before else. The nearest equivalent in C would be, say, trying to write do { break; }; while (1);

    • Dmitry 0

      Which in fact is entirely in the logic that semicolon is a delimiter, not end-of-statemeny mark, since

      <If_statement> ::=
      
        "if" <logical_expr> "then" <statement_1> ["else" <statement_2>].

      Nothing to separate here. Just like in your do…while example, before while part, where there’s nothing to mark end of, yes.

      Although for C it’s better to treat {} as a language construct separate from statements like if, while or for, since the rules for semicolon are not as consistent as in Pascal.

      P.S. Thanks, blog engine, for breaking EBNF.

      • 紅樓鍮 0

        The do-while loop in C is also consistent with if-else and other control statements:

        <do-while> ::= "do" <stmt> "while" "(" <expr> ")" ";"

        the <stmt> can be any statement, including a simple statement that ends in semicolon:

        do
          stmt;
        while (expr);

        the only places I can think of where braces are required are function bodies and switch statements. In those cases, you can consider the tokens "{" and "}" to directly belong to the grammars for <func-def> and <switch-stmt> respectively, and then <compound-stmt> becomes completely consistent with <stmt>.

        • Dmitry 0

          Well, not really. I mean, if we say that semicolon is a statement terminator (which it mostly is) and call {} a compound statement (but still statement, just like if or while) then there’s a problem:

          if (some_expr)
          {
            ...
          }
          else
          {
            ...
          }

          If braces are compound statement and statements are terminated with semicolon, then we should put semicolons after both closing braces. That would work for the else-branch (although for completely different reason called ”empty statement”) but something goes wrong with the first branch (what do you call it, guys? we call it then(zen)-branch).

          To make it at least feel more consistent, it helps thinking that closing brace implicitly contains the semicolon. Still not as consistent as in Pascal though.

          • 紅樓鍮 0

            Yes, the real inconsistency is in that some statements end in a semicolon while others don’t (most notably { ... }, but things like if (...) ... else ... also themselves don’t have the semicolon for that matters 🙂

  • Nathan Mates 0

    JSON5 allows the trailing commas, etc. It’s available as a library/package/etc for most of your favorite programming languages.

  • George Byrkit 0

    IIRC, PL/1 was also a ‘semi-colon is statement separator’ language. Made porting from C to PL/1 slightly harder, as you had semicolons to eliminate.

  • Frédéric B. 1

    This is one of the three reasons I’m baffled that .NET Core switched from XML to JSON for config files (the other two being retro-compatibility and vanilla JSON disallowing comments).

  • Johan Sköld 0

    Zig also supports trailing comma in parameter lists. Which may appear useless at first glance, but there is a neat side-effect you get from it: If you pass a file into `zig fmt` it will format that file for you. For parameter lists it puts them all on one line. But if the parameter list has a trailing comma, it will instead put them one on each line. So without trailing comma:

    fn add(a: i32, b: i32) i32 {
        return a + b;
    }

    With trailing comma:

    fn add(
        a: i32,
        b: i32,
    ) i32 {
        return a + b;
    }

Feedback usabilla icon