All questions

Base Converter

Premium

Base Converter

A number's base (or radix) is how many distinct digits it uses before carrying into the next column: base 10 has ten digits 0-9, binary has two, hexadecimal has sixteen (0-9 then a-f). baseConverter(str, fromBase, toBase) reads the integer written in str using fromBase's digits and rewrites it using toBase's digits, returning a string. Bases run from 2 to 36 — after 9 the digits continue a (value 10) through z (value 35). See radix for background.

Signature

baseConverter(
  str: string,      // the number, written in fromBase
  fromBase: number, // 2..36 — the base str is written in
  toBase: number,   // 2..36 — the base to convert to
): string           // the number rewritten in toBase, lowercase

Examples

baseConverter('ff', 16, 10);   // '255'
baseConverter('255', 10, 16);  // 'ff'
baseConverter('1010', 2, 10);  // '10'
baseConverter('z', 36, 10);    // '35'
baseConverter('-ff', 16, 10);  // '-255'   (sign preserved)
baseConverter('9', 8, 10);     // throws RangeError — 9 is not a base-8 digit

Notes

  • Digits0-9 cover values 0 through 9, then a-z cover 10 through 35. Input is case-insensitive (FF and ff both parse); output is always lowercase.
  • Validate the bases — both fromBase and toBase must be integers in 2..36. Anything else (base 1, base 37, 2.5) throws a RangeError.
  • Validate the digits — every digit in str must have a value below fromBase. A stray 9 in a base-8 number, or a g in hex, throws a RangeError. Do not silently stop reading at the first bad digit.
  • Sign — an optional leading - is preserved on the output. The value 0 is always "0", never "-0".
  • Range — inputs fit within Number.MAX_SAFE_INTEGER (2^53 − 1), so plain number arithmetic is enough; arbitrarily large inputs are out of scope.
  • Do it yourself — the point is the parse-then-emit algorithm, so don't reach for parseInt or Number.prototype.toString to do the conversion.

FAQ

Why route through a plain integer instead of mapping digits directly?
There is no direct character-to-character mapping between arbitrary bases — a base-16 f has no single base-10 digit. Parsing the source into the actual integer value it represents, then emitting that value in the target base, reduces every base pair to the same two steps and is why the algorithm is base-agnostic.
Why not just use parseInt(str, fromBase).toString(toBase)?
It works on clean input but parseInt stops at the first character it cannot read instead of throwing, so parseInt('1g', 16) returns 1 and hides an invalid digit. It also skips the base validation and the parse-and-emit loops the question is really testing. It breaks above 2^53 as well.
Why reverse the digits when emitting?
Repeated division produces digits from least-significant to most-significant: 500 in base 8 yields remainders 4, then 6, then 7. The written number needs them most-significant first, so you collect the remainders and reverse before joining, turning 4,6,7 into 764.

Unlock the solution & editor

  • Runnable editor + tests

    Solve in the browser with instant Jest feedback.

  • Detailed solutions

    Walkthroughs, edge cases, and complexity notes.

  • Multi-framework variants

    React, Vue, Vanilla, Angular — same question, different stacks.

Upgrade to Premium