Certified Associate JavaScript Programmer study materials
Certified Associate JavaScript Programmer study resources

Price Table Sorts Lexicographically and Produces NaN

Certified Associate JavaScript Programmer · Put your knowledge to work in context.

All case studies

Price Table Sorts Lexicographically and Produces NaN

A price table loads five strings from a JSON endpoint: ['10', '2', '100', '25', '5']. The developer writes:

const sorted = prices.sort();
const numbers = sorted.map(parseInt);

The table shows 10, 100, 2, 25, 5, and several cells display NaN. A second developer says sort() is fine because the values are numeric strings and parseInt is the standard converter. The table must display ascending numeric prices, and the same helper will later receive values such as '3.5', '', and '1e3' from user input. The endpoint sometimes returns an empty string for a missing price, and the UI must show a dash rather than $0. The team needs a fix that does not silently coerce empty input to zero.

1. Why does `prices.sort()` produce `10, 100, 2, 25, 5` instead of numeric ascending order?

Read the worked answer

Without a comparator, Array.prototype.sort converts elements to strings and compares their UTF-16 code unit sequences. It is lexicographic, not numeric. The first character of '10' is '1', the first character of '2' is '2', and '1' sorts before '2'. Within the '1' group, '10' comes before '100' by further character comparison.

The fact that the values originated as numeric strings is irrelevant; the default sort never parses numbers. To sort numerically, supply a comparator that returns a negative number, zero, or a positive number:

prices.sort((a, b) => Number(a) - Number(b));

The comparator must handle invalid or missing values deliberately. Number('') is 0, and Number('1e3') is 1000, which may or may not be desired. If a missing value should sort last or remain a dash, filter or map it before sorting rather than relying on coercion.

2. Why does `sorted.map(parseInt)` produce `NaN` entries? Show the argument flow.

Read the worked answer

Array.prototype.map calls its callback with three arguments: the element, the index, and the array. parseInt accepts two arguments: the string and an optional radix. When passed directly, map supplies the index as the radix.

So the calls are effectively:

parseInt('10', 0);
parseInt('100', 1);
parseInt('2', 2);
parseInt('25', 3);
parseInt('5', 4);

A radix of 0 means "infer base 10" in modern engines, so the first value can work. A radix of 1 is invalid and returns NaN. Other small radices reject digits that are out of range, so several entries become NaN or are partially parsed. The third argument, the array, is ignored by parseInt but does not cause the failure.

The bug is not that parseInt is unreliable; it is that map passes more arguments than the developer intended. Use an explicit wrapper: sorted.map(value => parseInt(value, 10)).

3. Write a robust conversion and sorting approach that handles `'3.5'`, `''`, and `'1e3'`, and explain the display rule.

Read the worked answer

Separate parsing from rendering. For each raw value, decide whether it is present, then convert with a deliberate numeric function:

function parsePrice(raw) {
  if (typeof raw !== 'string' || raw.trim() === '') return null;
  const n = Number(raw);
  return Number.isFinite(n) ? n : null;
}

const rows = prices.map(raw => ({ raw, value: parsePrice(raw) }));
rows.sort((a, b) => {
  if (a.value === null && b.value === null) return 0;
  if (a.value === null) return 1;
  if (b.value === null) return -1;
  return a.value - b.value;
});

Number('3.5') gives 3.5, and Number('1e3') gives 1000. Number('') gives 0, which is why the empty check must come first. Number.isFinite rejects NaN and Infinity from junk input.

The display rule then uses the parsed value: if it is null, render a dash; otherwise format the number as currency. Do not sort the original strings after this mapping; sort the row objects by value. This keeps missing data distinct from a legitimate zero price.