Strings never change in place: trim, toUpperCase, replaceAll, slice and friends all return a new string. Chain them to clean messy input, and use split with a regular expression such as /\s+/ to break text on any amount of whitespace. at(-1) reads the last character, and includes/startsWith answer simple questions without a regex.
Template literals (${value} inside backticks) build text, and padEnd/padStart line it up in columns. TypeScript adds template literal types: the type Term ${1 | 2 | 3} (written between backticks in code, as below) accepts only "Term 1", "Term 2" or "Term 3", so a typo becomes a compile error instead of a bug. Intl.NumberFormat formats money and percentages correctly for any locale.
A Date is one moment in time, stored in UTC. Parse unambiguous ISO strings with an offset (2026-03-14T09:30:00+03:00), display with Intl.DateTimeFormat and a timeZone, and compare with getTime(). Text typed by people needs checking: new Date happily turns 31/02 into 3 March, so validate the parts yourself and return null (typed as Date | null) when the input is not a real date.
// Strings are immutable: every method returns a new string.
const raw = " juma SAID , form 4 ";
const name = raw.split(",")[0].trim().split(/\s+/).map(capitalize).join(" ");
console.log(JSON.stringify(name)); // "Juma Said"
function capitalize(word: string): string {
return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase();
}
const sentence = "Photosynthesis happens in the chloroplasts of green plants.";
console.log(sentence.includes("green"), sentence.startsWith("Photo"), sentence.at(-1)); // true true .
console.log(sentence.replaceAll("s", "S").slice(0, 14)); // PhotoSyntheSiS
// Template literal types catch typos in string formats at compile time.
type Term = `Term ${1 | 2 | 3}`;
const term: Term = "Term 2";
// const bad: Term = "Term 4"; // error: not assignable to type Term
// Neat columns with padEnd / padStart
interface Row { student: string; score: number; balance: number }
const rows: Row[] = [
{ student: "Amina Hassan", score: 88, balance: 0 },
{ student: "Juma Said", score: 42, balance: 200_000 },
];
const tzs = new Intl.NumberFormat("en-TZ", { style: "currency", currency: "TZS", maximumFractionDigits: 0 });
console.log(`${"Name".padEnd(14)}${"Score".padStart(6)}${"Balance".padStart(14)} (${term})`);
for (const r of rows) {
console.log(`${r.student.padEnd(14)}${String(r.score).padStart(6)}${tzs.format(r.balance).padStart(14)}`);
}
// Dates: a Date is one moment in time. Months are 0-based in the constructor.
const registered = new Date("2026-03-14T09:30:00+03:00");
const exams = new Date("2026-11-02T08:00:00+03:00");
const DAY_MS = 24 * 60 * 60 * 1000;
console.log(Math.floor((exams.getTime() - registered.getTime()) / DAY_MS), "days to exams"); // 232
const eat = new Intl.DateTimeFormat("en-GB", {
dateStyle: "full", timeStyle: "short", timeZone: "Africa/Dar_es_Salaam",
});
console.log(eat.format(registered));
console.log(registered.toISOString()); // 2026-03-14T06:30:00.000Z (always UTC)
// Parse user input defensively: invalid dates are NaN, not exceptions.
function parseDayMonthYear(text: string): Date | null {
const m = /^(\d{1,2})\/(\d{1,2})\/(\d{4})$/.exec(text.trim());
if (!m) return null;
const [day, month, year] = [Number(m[1]), Number(m[2]), Number(m[3])];
const d = new Date(Date.UTC(year, month - 1, day));
// Reject dates JavaScript "rolls over", e.g. 31/02 becoming 3 March
return d.getUTCDate() === day && d.getUTCMonth() === month - 1 ? d : null;
}
console.log(parseDayMonthYear("14/03/2008")?.toISOString().slice(0, 10), parseDayMonthYear("31/02/2008"));Key points
- String methods return new strings; combine
trim,split(/\s+/),mapandjointo clean text. - Template literal types restrict strings to a pattern at compile time.
- Display dates with
Intl.DateTimeFormatand atimeZone; validate user-typed dates and returnDate | null.
Exercise
Write and export initials(fullName) ("amina hassan" → "A.H."), maskPhone(phone) (any 10-digit number with spaces or dashes → "0712***678", throwing for anything else) and ageOn(bornIso, onIso). Print an aligned table of initials, masked phone and age for three students.
Show solution
Try the exercise yourself first — then compare your approach with this one.
initials splits on any whitespace, so double spaces don't create empty parts. maskPhone first strips everything that isn't a digit with replace(/\D/g, ""), then refuses anything that isn't exactly 10 digits rather than printing nonsense. ageOn works on the ISO parts directly and subtracts a year if this year's birthday hasn't happened yet, which avoids time-zone surprises entirely.
interface Student {
name: string;
phone: string;
born: string; // ISO date, e.g. "2008-03-14"
}
export function initials(fullName: string): string {
return fullName
.trim()
.split(/\s+/)
.map((part) => part[0].toUpperCase() + ".")
.join("");
}
export function maskPhone(phone: string): string {
const digits = phone.replace(/\D/g, "");
if (digits.length !== 10) throw new Error(`expected 10 digits, got "${phone}"`);
return `${digits.slice(0, 4)}***${digits.slice(-3)}`;
}
export function ageOn(bornIso: string, onIso: string): number {
const [by, bm, bd] = bornIso.split("-").map(Number);
const [y, m, d] = onIso.split("-").map(Number);
const hadBirthday = m > bm || (m === bm && d >= bd);
return y - by - (hadBirthday ? 0 : 1);
}
const students: Student[] = [
{ name: "amina hassan", phone: "0712 345 678", born: "2008-03-14" },
{ name: "Juma Said", phone: "0754-000-111", born: "2009-11-30" },
{ name: "neema kimaro", phone: "0688222333", born: "2008-12-01" },
];
const today = "2026-10-05";
console.log(`${"Initials".padEnd(10)}${"Phone".padEnd(13)}${"Age".padStart(4)}`);
for (const s of students) {
console.log(`${initials(s.name).padEnd(10)}${maskPhone(s.phone).padEnd(13)}${String(ageOn(s.born, today)).padStart(4)}`);
}