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
tsccompiles your.tsfile 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 onlylet age = 32;, with no trace thatagewas ever typed. At runtime, a TypeScriptnumberis a plain JavaScript number and nothing more;typeof agestill reports"number"because that’s how JavaScript’stypeofoperator 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 initializer0, infersnumber, and locks that type in forcount‘s lifetime. Reassigningcount = "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
5has type5(a subtype ofnumber) before it gets widened. Withlet x = 5;, TypeScript widens the literal type5to the general typenumber, becauseletvariables can be reassigned. Withconst x = 5;, there’s no reassignment possible, so the type stays as the literal5. This is whyconstis useful for building precise union types liketype 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 forisMemberitself (it’s alreadyboolean), 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 typesNumber,String,Boolean, and avoidnew 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,
fetchresponses, environment variables, URL params) explicitly withNumber(),String(), or a validation library before treating it as the type you need — don’t rely on implicit coercion. - Prefer
constoverletwhen 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 asbooleanand set it with an actual comparison or theBoolean()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 accidentalstring+numbermistakes.
Practice Exercises
- Exercise 1: Write a function
celsiusToFahrenheit(celsius: number): numberthat converts a Celsius temperature to Fahrenheit using the formula(celsius * 9/5) + 32. Call it with0and confirm it logs32. - Exercise 2: Write a function
isStrongPassword(password: string): booleanthat returnstrueonly if the password’s length is 8 or greater. Test it against"abc"(expectfalse) and"correcthorse"(expecttrue). - Exercise 3: Given
const rawAge: string = "25";, write code that converts it to anumber, adds5to 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, andbooleanare 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
typeofbehaves exactly as it does in ordinary JavaScript. - Mixing types (like
stringandnumberwith+) 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.
