Skip to the code
cf.

Format a list

Write formatList(items, options). It joins an array of strings into a list that reads like English, and returns it as a string.

No items give "", and one item is returned as it is. Two items are joined with and , without a comma: Ada and Grace. Three or more are separated by , , with and before the last one and a comma before the and too: Ada, Grace, and Alan.

options is optional, and so is each of its properties. conjunction is the word before the last item: "and" by default, or "or" to list choices, as in tea, coffee, or juice.

limit is the most items to show, 1 or more; by default there's no limit. When there are more items than that, show the first limit items, then a count of the rest as the last item: Ada, Grace, and 3 others. A count of 1 is 1 other.

Don't change items, and don't use Intl.ListFormat.

formatList([]);                       // ""
formatList(["Ada"]);                  // "Ada"
formatList(["Ada", "Grace"]);         // "Ada and Grace"
formatList(["Ada", "Grace", "Alan"]); // "Ada, Grace, and Alan"
formatList(["tea", "coffee", "juice"], { conjunction: "or" });
// "tea, coffee, or juice"
formatList(["Ada", "Grace", "Alan", "Linus", "Barbara"], { limit: 2 });
// "Ada, Grace, and 3 others"
formatList(["Ada", "Grace", "Alan"], { limit: 2 });
// "Ada, Grace, and 1 other"

What the tests check

  1. returns "" for no items, and one item as it is
    expect(formatList([])).toBe("");
    expect(formatList(["Ada"])).toBe("Ada");
    expect(formatList(["Ada"], { conjunction: "or" })).toBe("Ada");
    
  2. joins two items with and, without a comma
    expect(formatList(["Ada", "Grace"])).toBe("Ada and Grace");
    expect(formatList(["salt", "pepper"])).toBe("salt and pepper");
    
  3. separates three or more with commas, including one before and
    expect(formatList(["Ada", "Grace", "Alan"])).toBe("Ada, Grace, and Alan");
    expect(formatList(["a", "b", "c", "d", "e"])).toBe("a, b, c, d, and e");
    
  4. uses the given conjunction
    expect(formatList(["tea", "coffee"], { conjunction: "or" })).toBe("tea or coffee");
    expect(formatList(["tea", "coffee", "juice"], { conjunction: "or" })).toBe("tea, coffee, or juice");
    expect(formatList(["red", "green", "blue"], {})).toBe("red, green, and blue");
    
  5. shows at most limit items, then a count of the others
    expect(formatList(["Ada", "Grace", "Alan", "Linus", "Barbara"], { limit: 2 })).toBe("Ada, Grace, and 3 others");
    expect(formatList(["a", "b", "c"], { limit: 1 })).toBe("a and 2 others");
    expect(formatList(["a", "b", "c", "d", "e"], { limit: 3, conjunction: "or" })).toBe("a, b, c, or 2 others");
    
  6. says 1 other when one item is left over
    expect(formatList(["Ada", "Grace", "Alan"], { limit: 2 })).toBe("Ada, Grace, and 1 other");
    expect(formatList(["a", "b"], { limit: 1 })).toBe("a and 1 other");
    
  7. shows every item when they fit within limit
    expect(formatList(["a", "b", "c"], { limit: 3 })).toBe("a, b, and c");
    expect(formatList(["a", "b"], { limit: 5 })).toBe("a and b");
    
  8. doesn't change the array
    const items = ["a", "b", "c", "d"];
    formatList(items);
    formatList(items, { limit: 2 });
    expect(items).toEqual(["a", "b", "c", "d"]);
    const frozen = Object.freeze(["x", "y", "z"]);
    expect(formatList(frozen)).toBe("x, y, and z");
    expect(formatList(frozen, { limit: 1 })).toBe("x and 2 others");
    
  9. works without Intl.ListFormat
    const ListFormat = Intl.ListFormat;
    Intl.ListFormat = function () {
      throw new Error("formatList must not use Intl.ListFormat");
    };
    let results;
    try {
      results = [formatList(["a", "b", "c"]), formatList(["a", "b"], { conjunction: "or" })];
    } finally {
      // Put the built-in back even when formatList throws, before expect runs.
      Intl.ListFormat = ListFormat;
    }
    expect(results).toEqual(["a, b, and c", "a or b"]);
    

Learn more

solution.jsSaved in this browser

Run tests runs every test on your code, here in your browser. The problem is solved when they all pass.

Ctrl + Enter runs the tests (⌘ + Enter on a Mac).