TypeScript Number String Boolean

Every value in a TypeScript program has a type, and the three most common types you will annotate are number, string, and boolean. They map directly onto JavaScript’s primitive values, but TypeScript adds a compile-time layer on top: it tracks which of these types a variable holds, checks that you only use values in ways that make sense for their type, and flags mistakes before your code ever runs. This lesson covers how these three primitive types work, how TypeScript infers and narrows them, the syntax for declaring them, and the mistakes beginners commonly make.

Overview / How it works

number, string, and boolean are TypeScript’s names for JavaScript’s three most-used primitive kinds of value. There is no separate int, float, or double in TypeScript — all numeric values, whether whole numbers, decimals, negatives, or numbers written in hexadecimal or binary notation, share the single type number. Text of any length, including empty strings and multi-line template literals, is string. And there are exactly two boolean values, true and false, both of type boolean.

These are called primitive types because they represent immutable, copy-by-value data — as opposed to object types like arrays, functions, and classes, which are copied by reference. TypeScript deliberately lower-cases these type names (number, not Number) to distinguish the primitive type from the built-in JavaScript wrapper object types (Number, String, Boolean), which you almost never want to use directly. More on that in Common Mistakes below.

TypeScript uses structural typing for these primitives: a value is compatible with a type if its shape matches, not because it was explicitly declared as that type. In practice this mostly matters for object types, but it explains why, for example, a string literal like "admin" is assignable to a variable typed string without any special conversion — the literal type "admin" is a more specific subtype of string.

TypeScript also performs type inference: when you initialize a variable with a value and don’t write an explicit annotation, the compiler infers the type from that value and remembers it for the rest of the variable’s lifetime. Writing let age = 30; is exactly as type-safe as writing let age: number = 30; — the compiler infers age: number either way, and will reject age = "thirty" in both cases. Explicit annotations become necessary mainly for function parameters (which have no initializer to infer from), for variables you plan to assign later, or when you want to widen or narrow a type intentionally.

Syntax

The general form is a colon followed by the type name, either on a variable declaration or a function signature:

let variableName: number = 42;
let text: string = "hello";
let flag: boolean = true;

let inferredNum = 42;        // inferred as number
let inferredText = "hello";  // inferred as string
let inferredFlag = true;     // inferred as boolean

console.log(typeof variableName, typeof text, typeof flag);

Output:

number string boolean
Type Example values Notes
number 42, -3.14, 0xff, 0b1010, NaN, Infinity One numeric type for all integers and floats; internally double-precision floating point
string "hi", 'hi', `hi ${name}` Any of the three JS quoting styles are valid; strings are immutable
boolean true, false Exactly two values; not the same as JS “truthy”/”falsy”

Because tsc infers types from initializers, most real-world code only needs explicit annotations on function parameters and return types, plus variables declared without an initial value.

Examples

Example 1: Declaring and inferring the three types

let age: number = 32;
let price: number = 19.99;
let temperature: number = -4;
let hex: number = 0xff;
let binary: number = 0b1010;

let firstName: string = "Ada";
let lastName: string = 'Lovelace';
let greeting: string = `Hello, ${firstName} ${lastName}!`;

let isActive: boolean = true;
let hasErrors: boolean = false;

console.log(age, price, temperature, hex, binary);
console.log(greeting);
console.log(isActive, hasErrors);

Output:

32 19.99 -4 255 10
Hello, Ada Lovelace!
true false

Notice that hex and binary are declared using different numeric literal notations, but both still have type number — TypeScript doesn’t distinguish numeric bases at the type level, only at the literal-syntax level. The template literal for greeting is still just a string once evaluated.

Example 2: Using the types in function signatures

function calculateTotal(price: number, quantity: number, taxRate: number): number {
  const subtotal = price * quantity;
  return subtotal + subtotal * taxRate;
}

function formatCurrency(amount: number): string {
  return `$${amount.toFixed(2)}`;
}

function isEligibleForDiscount(totalSpent: number, isMember: boolean): boolean {
  return isMember && totalSpent > 100;
}

const total = calculateTotal(24.99, 3, 0.08);
console.log(formatCurrency(total));
console.log(isEligibleForDiscount(total, true));
console.log(isEligibleForDiscount(total, false));

Output:

$80.97
false
false

Here the parameter and return type annotations do real work: if you accidentally called calculateTotal("24.99", 3, 0.08), tsc would reject it immediately with an error, instead of letting a stray string silently corrupt the arithmetic the way plain JavaScript would. isEligibleForDiscount returns false both times because the computed total of 80.9676 is below the 100 threshold.

Example 3: Combining the types in a realistic object

interface UserProfile {
  username: string;
  age: number;
  isVerified: boolean;
}

function describeUser(user: UserProfile): string {
  const status: string = user.isVerified ? "verified" : "unverified";
  return `${user.username} (age ${user.age}) is ${status}.`;
}

const users: UserProfile[] = [
  { username: "codewiz", age: 27, isVerified: true },
  { username: "newbie99", age: 19, isVerified: false },
];

for (const user of users) {
  console.log(describeUser(user));
}

const averageAge: number = users.reduce((sum: number, u: UserProfile) => sum + u.age, 0) / users.length;
console.log(`Average age: ${averageAge}`);

Output:

codewiz (age 27) is verified.
newbie99 (age 19) is unverified.
Average age: 23

This is how the three primitives usually appear in real code: not in isolation, but as the fields of an interface that models a piece of domain data. TypeScript checks every field access (user.username, user.age, user.isVerified) against the declared shape, and the array type UserProfile[] ensures every element in users satisfies it.

Under the hood

It helps to understand exactly what the compiler is doing and, just as importantly, what it is not doing:

  • Type checking happens only at compile time. When tsc compiles your .ts file to JavaScript, every type annotation — : number, : string, : boolean, interface declarations, generic parameters — is stripped out entirely. This is called type erasure. The compiled JavaScript for Example 1 contains only let age = 32;, with no trace that age was ever typed. At runtime, a TypeScript number is a plain JavaScript number and nothing more; typeof age still reports "number" because that’s how JavaScript’s typeof operator has always worked, independent of TypeScript.
  • Inference happens once, at the point of declaration. When you write let count = 0;, the compiler looks at the initializer 0, infers number, and locks that type in for count‘s lifetime. Reassigning count = "zero" later is an error even though nothing at runtime would stop it — the check exists purely in the type checker’s model of your program.
  • Literal types are more specific than the general primitive. The literal 5 has type 5 (a subtype of number) before it gets widened. With let x = 5;, TypeScript widens the literal type 5 to the general type number, because let variables can be reassigned. With const x = 5;, there’s no reassignment possible, so the type stays as the literal 5. This is why const is useful for building precise union types like type Direction = "up" | "down";.
  • Boolean narrowing feeds control flow analysis. When you check if (isMember), later code inside that branch doesn’t get a different type for isMember itself (it’s already boolean), but the compiler does use boolean expressions to narrow the types of other variables, which is the basis for type guards.

Common Mistakes

Mistake 1: Using the wrapper object types instead of the primitives

JavaScript has boxed wrapper objects Number, String, and Boolean in addition to the primitives. TypeScript exposes types for these wrappers too, and because they’re capitalized just like a custom class would be, beginners sometimes reach for them by mistake:

let count: number = new Number(5);
// Error: Type 'Number' is not assignable to type 'number'.
// 'number' is a primitive, but 'Number' is a wrapper object. Prefer using 'number' when possible.

The wrapper type Number describes the boxed object produced by new Number(5), which is a different runtime value from the primitive 5 — it’s an object, so typeof reports "object", comparisons behave oddly, and it carries needless overhead. tsc refuses to assign it to a number-typed variable. The fix is to always use the lower-case primitive type and skip the new wrapper constructors entirely:

let count: number = 5;
count = count + 1;
console.log(count);

Output:

6

Mistake 2: Mixing strings and numbers with the + operator

In plain JavaScript, + between a string and a number silently concatenates them, which is a frequent source of bugs (like a total of "1020" instead of 30). TypeScript’s static types catch this at compile time instead of letting it slip into production:

function addTax(price: string, taxRate: number): number {
  return price + taxRate;
}
// Error: Operator '+' cannot be applied to types 'string' and 'number'.

Here price was declared (perhaps because it came from a form input or URL parameter) as string, but the function tries to use it as if it were numeric. The compiler catches the mismatch immediately. The fix is to explicitly convert the string to a number first, using Number() or parseFloat(), before doing arithmetic:

function addTax(price: string, taxRate: number): number {
  const numericPrice = Number(price);
  return numericPrice + numericPrice * taxRate;
}

console.log(addTax("50", 0.2));

Output:

60

This pattern — data arriving as string from an external source (form fields, fetch responses, URL query parameters, environment variables) and needing an explicit conversion before numeric use — is one of the most common places string/number type errors show up in real applications, and it is exactly the kind of bug TypeScript is designed to surface early.

Best Practices

  • Always use the lower-case primitive type names — number, string, boolean — never the capitalized wrapper types Number, String, Boolean, and avoid new Number(), new String(), new Boolean() in your code entirely.
  • Let inference do the work for local variables with an initializer (let count = 0;); reserve explicit annotations for function parameters, return types, and variables declared without an initial value.
  • Convert external input (form values, fetch responses, environment variables, URL params) explicitly with Number(), String(), or a validation library before treating it as the type you need — don’t rely on implicit coercion.
  • Prefer const over let when a variable never gets reassigned; besides being good JS practice, it lets TypeScript infer narrower literal types where useful.
  • Remember that a JavaScript “truthy” value (like a non-empty string or a non-zero number) is not the same as the type boolean — if a value needs to behave as a real boolean, declare it as boolean and set it with an actual comparison or the Boolean() function, not just any truthy expression.
  • Use template literals (`${a} ${b}`) instead of string concatenation with + when building strings from multiple typed values — it reads more clearly and sidesteps accidental string + number mistakes.

Practice Exercises

  • Exercise 1: Write a function celsiusToFahrenheit(celsius: number): number that converts a Celsius temperature to Fahrenheit using the formula (celsius * 9/5) + 32. Call it with 0 and confirm it logs 32.
  • Exercise 2: Write a function isStrongPassword(password: string): boolean that returns true only if the password’s length is 8 or greater. Test it against "abc" (expect false) and "correcthorse" (expect true).
  • Exercise 3: Given const rawAge: string = "25";, write code that converts it to a number, adds 5 to it, and logs the result as a full sentence using a template literal, e.g. "In 5 years you will be 30.".

Summary

  • number, string, and boolean are TypeScript’s names for JavaScript’s core primitive value kinds; there is only one numeric type, no separate int/float.
  • TypeScript infers these types automatically from initializers, so explicit annotations are mainly needed on function parameters, return types, and uninitialized variables.
  • Always use the lower-case primitive names, never the capitalized wrapper object types (Number, String, Boolean).
  • All type information is erased at compile time — the emitted JavaScript contains only plain values, and typeof behaves exactly as it does in ordinary JavaScript.
  • Mixing types (like string and number with +) is a compile-time error in TypeScript, catching a whole class of bugs that plain JavaScript would silently coerce.
  • Convert external data explicitly before using it as a different type — don’t rely on implicit coercion.