FullStack Course LogoFullStack Course
Module: JavaScript
JavaScriptยท062ยท10 MIN READ

062: Standard Built-ins: String, Number, Math, Date, Intl, and RegExp

TOPICS COVERED: Standard Built-ins: String, Number, Math, Date, Intl, and RegExp

Outcomes

By the end of this lesson, you can:

  • use core built-in objects intentionally;
  • manipulate strings without unnecessary mutation assumptions;
  • validate and format numbers;
  • use Math for numeric utilities;
  • handle dates without confusing display formatting with data storage;
  • format numbers and dates with Intl;
  • create and use regular expressions for suitable text patterns.

These objects are available before you add a library. The useful skill is not memorizing every method; it is choosing the built-in whose semantics match the problem, then recognizing the cases where it is not enough.

Strings

One source of confusion with strings is expecting a method call to change the variable you started with. Strings are immutable: an operation produces a new string rather than changing the existing string in place.

js
const name = "  tea shop  ";

const cleaned = name.trim().toUpperCase();

console.log(cleaned); // "TEA SHOP"
console.log(name);    // original unchanged

The chained calls operate on successive string values. trim() returns a string without the surrounding whitespace, and toUpperCase() returns another string based on that result. The value held by name remains unchanged, so assigning the result to cleaned is what keeps the transformed value.

Useful methods include:

js
"JavaScript".includes("Script");
"JavaScript".startsWith("Java");
"tea,coffee".split(",");
"hello".replace("h", "H");

These answer common questions and transformations: whether text contains or starts with a value, how to turn delimiter-separated text into an array, and how to produce a replacement string. None of them should be read as mutating the original string.

Use locale-aware behavior when human-language rules matter. Simple comparisons or case conversions can be adequate for protocol values and deliberately constrained identifiers, but user-facing language can have different sorting and casing rules depending on the locale.

Numbers

The Number methods are useful for checking the kind of numeric value you actually received. They are especially helpful at boundaries where input may be NaN, an infinity, or a non-integer:

js
Number.isFinite(12.5);
Number.isInteger(12);
Number.isNaN(NaN);

Do not assume that every value that looks numeric is safe to use in calculations. Validate the value for the operation you are about to perform, and remember that a number being finite does not make it an integer or make it an exact decimal.

Floating-point arithmetic can expose the way binary numbers represent decimal fractions:

js
0.1 + 0.2; // 0.30000000000000004

That result is a representation artifact, not a claim that ordinary decimal addition is wrong. For money, do not assume binary floating point is exact. Many systems model minor currency units as integers:

js
const unitPricePaise = 1050;
const quantity = 3;
const totalPaise = unitPricePaise * quantity;

Here the calculation is performed in paise, so the total is represented as an integer number of minor units. This is a modeling choice, not a universal fix: financial systems may require decimal libraries or domain-specific rounding rules. Decide based on the business requirements, including the currency and the rules for tax, discounts, and rounding.

Math

Math provides numeric utilities without requiring an instance. The rounding direction is part of each method's meaning:

js
Math.round(12.5);
Math.floor(12.9);
Math.ceil(12.1);
Math.max(10, 20, 5);
Math.min(10, 20, 5);
Math.abs(-42);

For example, floor moves toward the lower integer and ceil toward the higher integer. That distinction matters when converting a calculated quantity into a number of pages, slots, or units. Choose the operation that matches the domain rule instead of treating all rounding methods as interchangeable.

Random integer in a range:

js
function randomInt(min, max) {
  const lower = Math.ceil(min);
  const upper = Math.floor(max);

  return Math.floor(
    Math.random() * (upper - lower + 1)
  ) + lower;
}

The ceil and floor calls make the bounds usable even when the caller supplies non-integers. The + 1 makes the resulting integer range inclusive of both lower and upper, assuming the bounds describe a valid range.

Math.random() is not suitable for cryptographic security. It is appropriate for ordinary non-security-sensitive variation, but not for tokens, password material, session identifiers, or anything an attacker must not predict. Use crypto.getRandomValues() or platform security APIs for security-sensitive randomness.

Dates

Dates combine two ideas that developers often mix up. JavaScript Date represents an instant as milliseconds from the Unix epoch, while its APIs also expose calendar fields. The instant is the stored point in time; a calendar representation depends on a time zone.

js
const now = new Date();
console.log(now.toISOString());

toISOString() gives a machine-friendly UTC representation of that instant. It is useful for logs and data exchange because it is unambiguous, but it is not automatically the presentation format you want to show a user.

When possible, parse an unambiguous machine timestamp:

js
const createdAt = new Date("2026-08-27T10:30:00Z");

The Z identifies UTC, and the full date-time form makes the intended instant explicit. Avoid ambiguous strings such as:

js
new Date("08/09/2026");

Different readers may interpret the day/month order differently. Even where a particular runtime accepts a string consistently, the format does not communicate the intended domain meaning clearly enough for reliable data exchange.

Store versus Display

The useful distinction is between the value your system stores and the text a particular person sees. A good rule is:

  • store/transmit a well-defined timestamp or domain date;
  • format it for the user's locale at the UI boundary.
js
const formatter = new Intl.DateTimeFormat("en-IN", {
  dateStyle: "medium",
  timeStyle: "short",
});

console.log(formatter.format(createdAt));

The timestamp remains suitable for sorting and data exchange, while the formatter controls its human-facing representation. Do not store the locale-formatted output as if it were the source data; changing locale, time zone, or display requirements would then require parsing presentation text back into a value.

Intl.NumberFormat

Intl.NumberFormat handles locale-sensitive separators, currency conventions, and percent formatting. That is safer and clearer than manually inserting commas or concatenating a currency symbol.

Currency:

js
const inr = new Intl.NumberFormat("en-IN", {
  style: "currency",
  currency: "INR",
});

console.log(inr.format(123456.78));

Percent:

js
const percent = new Intl.NumberFormat("en-IN", {
  style: "percent",
  maximumFractionDigits: 1,
});

console.log(percent.format(0.1875));

The percent formatter expects a ratio, so 0.1875 is formatted as a percentage rather than as the literal number 0.1875. The locale and options determine details such as separators, symbols, and the permitted fractional precision.

Prefer Intl over manually inserting commas and currency symbols. Manual formatting tends to encode one locale's rules into application logic and becomes fragile as soon as the audience or display requirements change.

Intl.Collator

Default string sorting is not always the order a person expects. Intl.Collator lets you describe human-friendly comparison rules, including case sensitivity and numeric portions:

js
const collator = new Intl.Collator("en", {
  sensitivity: "base",
  numeric: true,
});

const names = ["item 10", "Item 2", "item 1"];

names.sort(collator.compare);

console.log(names);

With numeric: true, values such as item 2 and item 10 are compared by their numeric portions rather than by character order. sensitivity: "base" makes the comparison less concerned with case. Select the locale and options deliberately when the order is user-facing.

Regular Expressions

A regular expression describes a text pattern. It is a good fit when the allowed shape is constrained and can be expressed clearly:

js
const codePattern = /^[A-Z]{3}-\d{4}$/;

console.log(codePattern.test("ORD-1024")); // true

The anchors are significant. ^ requires the match to start at the beginning, and $ requires it to finish at the end, so extra text is not silently accepted.

Parts:

text
^          start
[A-Z]{3}   three uppercase letters
-          literal hyphen
\d{4}      four digits
$          end

Reading a pattern in parts is usually easier than treating it as an opaque line of punctuation. In this case, the complete input must have exactly three uppercase letters, a hyphen, and four digits.

Capturing Data

Testing whether text matches is different from extracting pieces of a match. match() can return the captured groups when the expression uses parentheses:

js
const match = "ORD-1024".match(/^([A-Z]{3})-(\d{4})$/);

if (match) {
  console.log(match[1]); // ORD
  console.log(match[2]); // 1024
}

The condition matters because match() returns null when the input does not match. Group numbering follows the opening parentheses, so match[1] is the prefix and match[2] is the numeric part.

Named groups make the extracted data easier to understand at the use site:

js
const result = "ORD-1024".match(
  /^(?<prefix>[A-Z]{3})-(?<number>\d{4})$/
);

console.log(result?.groups);

The names document the structure and avoid making later code depend on remembering group positions.

Do Not Use Regex for Everything

Regular expressions are excellent for constrained text patterns. They are poor substitutes for:

  • full HTML parsing;
  • JSON parsing;
  • complex URL parsing;
  • semantic email-address truth;
  • business validation requiring multiple fields.

Use specialized parsers and APIs where they exist. A regex can check a narrow shape, but it cannot reliably provide the semantics of an HTML parser, JSON parser, URL implementation, or a business rule that depends on several related values. An email-shaped string, for example, is not proof that the address exists or can receive mail.

Worked Example: Receipt Formatter

This example keeps the stored order data separate from its presentation. The number and date formatters are configured once, then used when the receipt text is assembled:

js
const money = new Intl.NumberFormat("en-IN", {
  style: "currency",
  currency: "INR",
});

const date = new Intl.DateTimeFormat("en-IN", {
  dateStyle: "medium",
  timeStyle: "short",
});

function formatReceipt(order) {
  return [
    `Order: ${order.id}`,
    `Total: ${money.format(order.total)}`,
    `Placed: ${date.format(new Date(order.placedAt))}`,
  ].join("\n");
}

console.log(
  formatReceipt({
    id: "ORD-1024",
    total: 845.5,
    placedAt: "2026-08-27T07:00:00Z",
  })
);

placedAt arrives as an ISO timestamp, is converted to a Date for formatting, and is not replaced with the formatted display string in the order object. The same pattern lets another caller provide a different locale or formatter configuration without changing the underlying order data.

Advanced Notes: Unicode, Time Zones, and Regex State

The basic APIs cover common cases, but three areas regularly cause bugs once code handles real user input and production data.

Strings are Unicode text, but indexing can be surprising

Some Unicode characters use more than one UTF-16 code unit. That means .length reports code units, not necessarily the number of code points or the number of characters a person perceives:

js
const emoji = "๐Ÿ˜€";

console.log(emoji.length); // 2
console.log([...emoji].length); // 1 code point

For user-visible text, do not assume .length always equals the number of characters a human perceives. Grapheme clusters can be more complex still: a visible character may consist of multiple code points. Intl.Segmenter can help where supported and needed, especially when the interface must count or split text in a user-oriented way.

Dates and time zones

A timestamp and a calendar date are different domain concepts. This is where people usually get confused: a value that looks like a date may not represent an instant at all.

"2026-08-27" may represent a local business date with no time-of-day meaning. Converting it blindly through Date and time zones can shift the visible date. The same issue appears with birthdays, holidays, and schedule-only values.

For business-day, birthday, or schedule-only values, decide explicitly whether the domain represents:

  • an instant;
  • a date;
  • a local date/time;
  • a recurring calendar rule.

Do not make Date choose the business semantics for you. Choose a representation and conversion policy that match the domain before formatting the value for a particular user.

Stateful regular expressions

Regexes with g or y can maintain lastIndex. That state belongs to the regex object, so repeated calls can depend on what happened before:

js
const pattern = /\d+/g;

console.log(pattern.test("123")); // true
console.log(pattern.lastIndex);

Reusing stateful regex objects without understanding this can create intermittent-looking bugs. For validation, anchored non-global regexes are often simpler because each test does not carry a search position from an earlier test.

Named capture and replacement

Named groups are also useful when a match is used to build another string:

js
const input = "2026-08-27";

const output = input.replace(
  /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/,
  "$<day>/$<month>/$<year>"
);

console.log(output);

This demonstrates regex capture mechanics and produces 27/08/2026. Prefer date formatting APIs for real date values; a replacement like this only rearranges text and does not validate a calendar date or apply time-zone rules.

Mistakes and Debugging

When behavior looks wrong, start by identifying whether the problem is the stored value, the operation, or the display layer. Common mistakes include:

  • assuming string methods mutate the original string;
  • formatting money with string concatenation only;
  • treating floating-point values as exact decimals;
  • storing locale-formatted dates as machine data;
  • parsing ambiguous date strings;
  • using Math.random() for tokens/passwords;
  • writing a giant regex where multiple validation steps would be clearer.

For a date or number bug, log the unformatted value and its type before inspecting the rendered text. For a regex bug, test a valid case, an invalid case with extra characters, and the smallest failing input; inspect whether g or y left lastIndex behind. For a security-sensitive random value, replace Math.random() with the platform's security API rather than trying to make the ordinary generator less predictable.

Exercises

Core

Use Intl.NumberFormat to format INR. Verify the result for a value with a fractional part and a value large enough to show locale-specific grouping.

Practice

Validate an order code ABC-1234. Make the pattern require exactly three uppercase letters, a hyphen, and exactly four digits, rather than merely finding that sequence inside a longer string.

Professional Extension

Write a report formatter that receives ISO timestamps and numeric totals and displays them using a caller-provided locale. Keep the incoming timestamp and numeric total as data, create locale-specific formatters at the presentation boundary, and consider what should happen when the timestamp is invalid.

Recap

Built-in objects solve common problems better than hand-written utilities when used with a clear understanding of their semantics and limitations. Strings produce new values, number operations have precision and validation boundaries, Math.random() is not a security primitive, dates require an explicit domain and time-zone model, Intl belongs at the display boundary, and regular expressions should stay focused on patterns they can describe reliably.

Reader page: /javascript/lesson/062/standard-built-ins-string-number-math-date-intl-and-regexp