Basic Types in TypeScript


Basic Types in TypeScript#
TypeScript's power comes from its type system, and it all starts with the basics. Understanding primitive types—string, number, boolean—and how to work with arrays is fundamental to writing type-safe code. These simple building blocks combine to create complex, reliable applications.
Why Types Matter: Type annotations catch bugs at compile time, improve IDE autocomplete, serve as documentation, and make refactoring safer. A few extra characters of type annotations save hours of debugging.
Understanding Type Annotations
Type annotations explicitly tell TypeScript what type a value should be. The syntax is simple: add a colon after the variable name, followed by the type.
let variableName: type = value;Basic example:
let userName: string = "Alice";
let age: number = 30;
let isActive: boolean = true;TypeScript validates these at compile time:
let userName: string = "Alice";
userName = 42; // Error: Type 'number' is not assignable to type 'string'Functions with type annotations:
function greet(name: string): string {
return `Hello, ${name}!`;
}
greet("Bob"); // ✓ Works
greet(123); // ✗ Error: Argument of type 'number' is not assignable to parameter of type 'string'The : string after the parameters specifies the parameter type, and : string after the parentheses specifies the return type.
The string Type
The string type represents textual data. In TypeScript, all text values—whether defined with single quotes, double quotes, or backticks—are type string.
let firstName: string = "Alice";
let lastName: string = "Smith";
let message: string = `Hello, world!`;Basic String Operations
TypeScript provides full type safety for string operations:
let greeting: string = "Hello";
let name: string = "Alice";
// Concatenation
let message: string = greeting + ", " + name; // "Hello, Alice"
// Methods
let upper: string = message.toUpperCase(); // "HELLO, ALICE"
let lower: string = message.toLowerCase(); // "hello, alice"
let length: number = message.length; // 12
// TypeScript knows these methods exist
let firstChar: string = message.charAt(0); // "H"
let substring: string = message.slice(0, 5); // "Hello"The IDE will autocomplete string methods and catch typos:
let text: string = "TypeScript";
text.toUppercase(); // Error: Property 'toUppercase' does not exist. Did you mean 'toUpperCase'?Template Literals
Template literals (backticks) support embedded expressions:
let name: string = "Alice";
let age: number = 30;
let bio: string = `My name is ${name} and I am ${age} years old.`;
// "My name is Alice and I am 30 years old."
// Expressions work too
let nextYear: string = `Next year I'll be ${age + 1}.`;
// "Next year I'll be 31."Multi-line strings are easy:
let poem: string = `
Roses are red,
Violets are blue,
TypeScript is awesome,
And so are you!
`;String vs string
TypeScript has both string (lowercase) and String (uppercase):
let primitive: string = "hello"; // ✓ Correct - use this
let object: String = new String("hello"); // ✗ Avoid - this is an object wrapperBest Practice: Always use lowercase string, number, and boolean. The
uppercase versions (String, Number, Boolean) are object wrapper types
that you should almost never use.
The number Type
TypeScript has a single number type for all numeric values—integers, floats, hex, binary, and octal numbers are all type number.
let integer: number = 42;
let float: number = 3.14159;
let negative: number = -10;Number Formats
TypeScript supports various number literals:
// Decimal
let decimal: number = 42;
// Hexadecimal (base 16)
let hex: number = 0x2a; // 42 in hex
// Binary (base 2)
let binary: number = 0b101010; // 42 in binary
// Octal (base 8)
let octal: number = 0o52; // 42 in octal
// Scientific notation
let scientific: number = 1e6; // 1,000,000
let small: number = 1e-6; // 0.000001
// All are type 'number'
console.log(typeof hex); // "number"You can use numeric separators for readability:
let billion: number = 1_000_000_000;
let creditCard: number = 1234_5678_9012_3456;Special Number Values
JavaScript (and TypeScript) has special number values:
let infinity: number = Infinity;
let negInfinity: number = -Infinity;
let notANumber: number = NaN;
// These are results of certain operations
let divideByZero: number = 1 / 0; // Infinity
let invalidMath: number = Math.sqrt(-1); // NaNCheck for these values:
function safeDivide(a: number, b: number): number | string {
if (b === 0) {
return "Cannot divide by zero";
}
return a / b;
}
// Check for NaN
if (isNaN(someNumber)) {
console.log("Not a valid number");
}
// Check for finite numbers
if (isFinite(someNumber)) {
console.log("Valid finite number");
}BigInt for Large Numbers
For integers larger than Number.MAX_SAFE_INTEGER (2^53 - 1), use bigint:
let big: bigint = 9007199254740991n; // Note the 'n' suffix
let huge: bigint = BigInt("9007199254740991");
// BigInt operations
let sum: bigint = big + 1n;
let product: bigint = big * 2n;
// Can't mix number and bigint
let mixed = big + 42; // Error: Operator '+' cannot be applied to types 'bigint' and 'number'
let correct = big + 42n; // ✓ WorksThe boolean Type
The boolean type has only two values: true and false.
let isActive: boolean = true;
let isCompleted: boolean = false;
let hasPermission: boolean = checkPermission(); // Function returns booleanCommon use cases:
// Conditional logic
let isLoggedIn: boolean = false;
if (isLoggedIn) {
console.log("Welcome back!");
} else {
console.log("Please log in.");
}
// Function return types
function isEven(num: number): boolean {
return num % 2 === 0;
}
console.log(isEven(4)); // true
console.log(isEven(7)); // false
// Comparison results are boolean
let isGreater: boolean = 10 > 5; // true
let isEqual: boolean = "hello" === "hi"; // falseTruthy and Falsy Values
JavaScript has "truthy" and "falsy" values, but TypeScript's type system focuses on actual booleans:
// These are falsy in JavaScript: false, 0, "", null, undefined, NaN
// But they're not type 'boolean'
let value: boolean = 0; // Error: Type 'number' is not assignable to type 'boolean'
let flag: boolean = ""; // Error: Type 'string' is not assignable to type 'boolean'
let check: boolean = undefined; // Error: Type 'undefined' is not assignable to type 'boolean'
// Explicitly convert to boolean
let isValid: boolean = Boolean(0); // false
let hasValue: boolean = !!someValue; // Double negation converts to booleanType Guards with Booleans
Use booleans to narrow types:
function processValue(value: string | null) {
let hasValue: boolean = value !== null;
if (hasValue) {
// TypeScript knows 'value' is string here
console.log(value.toUpperCase());
}
}Arrays in TypeScript
Arrays in TypeScript can be typed in two ways, both are equivalent:
// Method 1: Type[]
let numbers: number[] = [1, 2, 3, 4, 5];
// Method 2: Array<Type>
let strings: Array<string> = ["apple", "banana", "cherry"];Array Type Syntax
Simple arrays:
let ages: number[] = [25, 30, 35];
let names: string[] = ["Alice", "Bob", "Charlie"];
let flags: boolean[] = [true, false, true];TypeScript enforces array types:
let numbers: number[] = [1, 2, 3];
numbers.push(4); // ✓ Works
numbers.push("five"); // Error: Argument of type 'string' is not assignable to parameter of type 'number'
let mixed = [1, "two", true]; // Type: (string | number | boolean)[]Nested arrays:
let matrix: number[][] = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9],
];
let grid: string[][] = [
["X", "O", "X"],
["O", "X", "O"],
["O", "O", "X"],
];Arrays of specific lengths (tuples will be covered later):
let coordinates: [number, number] = [10, 20];
let rgb: [number, number, number] = [255, 128, 0];Array Methods
TypeScript provides full type safety for array methods:
let numbers: number[] = [1, 2, 3, 4, 5];
// Map - transforms each element
let doubled: number[] = numbers.map((n) => n * 2);
// [2, 4, 6, 8, 10]
// Filter - keeps elements that match condition
let evens: number[] = numbers.filter((n) => n % 2 === 0);
// [2, 4]
// Find - returns first match (or undefined)
let firstEven: number | undefined = numbers.find((n) => n % 2 === 0);
// 2
// Reduce - combines all elements
let sum: number = numbers.reduce((acc, n) => acc + n, 0);
// 15
// Some - checks if any element matches
let hasEven: boolean = numbers.some((n) => n % 2 === 0);
// true
// Every - checks if all elements match
let allPositive: boolean = numbers.every((n) => n > 0);
// trueType inference in callbacks:
let names: string[] = ["Alice", "Bob", "Charlie"];
// TypeScript knows 'name' is string
let uppercased = names.map((name) => {
return name.toUpperCase(); // name.toUpperCase() is valid
});
// Explicit types in callbacks (optional but clear)
let lengths: number[] = names.map((name: string): number => {
return name.length;
});Readonly Arrays
Prevent array modifications with readonly:
let numbers: readonly number[] = [1, 2, 3];
// or: let numbers: ReadonlyArray<number> = [1, 2, 3];
numbers[0] = 99; // Error: Index signature in type 'readonly number[]' only permits reading
numbers.push(4); // Error: Property 'push' does not exist on type 'readonly number[]'
// You can still use non-mutating methods
let doubled = numbers.map((n) => n * 2); // ✓ Works - creates new array
let first = numbers[0]; // ✓ Works - reading is allowedBest Practice: Use readonly for arrays that shouldn't be modified. This
prevents bugs and makes your intentions clear.
The any Type
The any type opts out of type checking—it can be anything:
let flexible: any = "hello";
flexible = 42; // No error
flexible = true; // No error
flexible = [1, 2, 3]; // No error
// No type checking on operations
flexible.anyMethodName(); // No error (runtime error if method doesn't exist)
let result = flexible.foo.bar; // No error (runtime error if properties don't exist)Avoid any When Possible: Using any defeats the purpose of TypeScript.
It removes all type safety and makes refactoring dangerous. Use it only as a
last resort or when migrating JavaScript code.
When any might be acceptable:
- Migrating JavaScript code incrementally
- Working with truly dynamic data (rare)
- Interfacing with untyped third-party libraries (better to use
unknownthough)
Type Inference
TypeScript can often infer types without explicit annotations:
// TypeScript infers these types
let message = "Hello"; // Type: string
let count = 42; // Type: number
let isActive = true; // Type: boolean
let numbers = [1, 2, 3]; // Type: number[]
// Hover over these in VS Code to see inferred types
let computed = count * 2; // Type: number
let greeting = `Hi, ${message}`; // Type: stringFunction return type inference:
// Return type inferred as string
function createGreeting(name: string) {
return `Hello, ${name}!`;
}
// Return type inferred as number
function add(a: number, b: number) {
return a + b;
}
// Return type inferred as string[]
function getNames() {
return ["Alice", "Bob", "Charlie"];
}When to add explicit types:
// Inference works well here
let name = "Alice";
// But be explicit for clarity in complex cases
let userData: { name: string; age: number } = {
name: "Alice",
age: 30,
};
// Explicit parameter types (required), explicit return type (optional but recommended for public APIs)
function processUser(user: { name: string; age: number }): string {
return `User ${user.name} is ${user.age} years old`;
}Best Practice: Let TypeScript infer types for simple variables, but add explicit annotations for function parameters and return types, especially in public APIs and complex objects.
Union Types with Primitives
Union types allow a value to be one of several types:
let id: string | number;
id = "ABC123"; // ✓ Works
id = 456; // ✓ Works
id = true; // Error: Type 'boolean' is not assignable
// Function with union type
function printId(id: string | number) {
console.log(`Your ID is: ${id}`);
}
printId("ABC123"); // ✓ Works
printId(456); // ✓ WorksWorking with union types:
function processValue(value: string | number) {
// Type narrowing with typeof
if (typeof value === "string") {
// TypeScript knows value is string here
return value.toUpperCase();
} else {
// TypeScript knows value is number here
return value.toFixed(2);
}
}Arrays with union types:
let mixed: (string | number)[] = [1, "two", 3, "four"];
// Or union of array types
let arrayOrNumber: number[] | number = [1, 2, 3];
arrayOrNumber = 42; // Also validLiteral Types
Literal types represent exact values:
// String literals
let direction: "north" | "south" | "east" | "west";
direction = "north"; // ✓ Works
direction = "up"; // Error: Type '"up"' is not assignable
// Number literals
let diceRoll: 1 | 2 | 3 | 4 | 5 | 6;
diceRoll = 3; // ✓ Works
diceRoll = 7; // Error
// Boolean literals (not super useful, but possible)
let alwaysTrue: true = true;
alwaysTrue = false; // Error
// Useful for function parameters
function setAlignment(align: "left" | "center" | "right") {
// Implementation
}
setAlignment("left"); // ✓ Works
setAlignment("middle"); // Error: Argument of type '"middle"' is not assignableCombining literals with primitives:
function compare(a: number, b: number): -1 | 0 | 1 {
if (a < b) return -1;
if (a > b) return 1;
return 0;
}The null and undefined Types
TypeScript treats null and undefined as distinct types:
let nothing: null = null;
let notDefined: undefined = undefined;
// These are different types
nothing = undefined; // Error (with strictNullChecks)
notDefined = null; // Error (with strictNullChecks)Optional values:
// These two are equivalent:
let name: string | null = null;
let age: number | undefined = undefined;
// Optional parameter (automatically adds | undefined)
function greet(name?: string) {
if (name) {
console.log(`Hello, ${name}!`);
} else {
console.log("Hello, stranger!");
}
}
greet("Alice"); // "Hello, Alice!"
greet(); // "Hello, stranger!"Null checking:
function getLength(str: string | null): number {
// Without check
// return str.length; // Error: 'str' is possibly 'null'
// With check
if (str === null) {
return 0;
}
return str.length; // TypeScript knows str is string here
}
// Optional chaining (modern approach)
let length = str?.length ?? 0; // Returns 0 if str is null/undefinedStrict Null Checks: Always enable strictNullChecks in your
tsconfig.json. It prevents the billion-dollar mistake of null pointer
errors.
Type Assertions
Sometimes you know more about a type than TypeScript does. Type assertions tell TypeScript to trust you:
// Syntax 1: as keyword (preferred)
let someValue: unknown = "this is a string";
let strLength: number = (someValue as string).length;
// Syntax 2: angle-bracket syntax (doesn't work in .tsx files)
let strLength2: number = (<string>someValue).length;Common use case:
// DOM manipulation
let inputElement = document.getElementById("username") as HTMLInputElement;
inputElement.value = "Alice"; // TypeScript knows this is an input element
// Without assertion, TypeScript only knows it's HTMLElement
let element = document.getElementById("username");
// element.value = "Alice"; // Error: Property 'value' does not exist on type 'HTMLElement'Use Sparingly: Type assertions bypass TypeScript's type checking. Use them only when you're certain about the type. Incorrect assertions lead to runtime errors.
Best Practices for Basic Types
1. Let TypeScript infer when obvious:
// ✗ Redundant
let name: string = "Alice";
// ✓ Better
let name = "Alice"; // TypeScript infers string2. Always type function parameters:
// ✗ Bad - parameters need types
function greet(name) {
return `Hello, ${name}!`;
}
// ✓ Good
function greet(name: string): string {
return `Hello, ${name}!`;
}3. Use strict mode:
// tsconfig.json
{
"compilerOptions": {
"strict": true,
"strictNullChecks": true
}
}4. Prefer unknown over any:
// ✗ Avoid
let data: any = fetchData();
// ✓ Better - forces type checking before use
let data: unknown = fetchData();
if (typeof data === "string") {
console.log(data.toUpperCase());
}5. Use const for immutable values:
const API_KEY = "abc123"; // Type: "abc123" (literal type)
const PORT = 3000; // Type: 3000 (literal type)
// vs
let apiKey = "abc123"; // Type: string
let port = 3000; // Type: numberCommon Mistakes to Avoid
Mistake 1: Using uppercase types
// ✗ Wrong
let name: String = "Alice";
let count: Number = 42;
// ✓ Correct
let name: string = "Alice";
let count: number = 42;Mistake 2: Forgetting arrays need type arguments
// ✗ Unclear
let items: Array = [1, 2, 3]; // Error
// ✓ Clear
let items: number[] = [1, 2, 3];
// or
let items: Array<number> = [1, 2, 3];Mistake 3: Ignoring null/undefined
// ✗ Unsafe (without strictNullChecks)
function getLength(str: string) {
return str.length;
}
getLength(null); // Runtime error!
// ✓ Safe
function getLength(str: string | null): number {
return str?.length ?? 0;
}Mistake 4: Overusing any
// ✗ Defeats the purpose of TypeScript
let data: any = fetchData();
data.nonExistentMethod(); // No error, but will crash at runtime
// ✓ Use proper types
interface UserData {
name: string;
age: number;
}
let data: UserData = fetchData();Next Steps
You now have a solid foundation in TypeScript's basic types! Here's what to explore next:
Immediate Next Steps:
- Practice writing functions with typed parameters
- Create typed arrays and use array methods
- Experiment with union types and literal types
- Enable strict mode in your projects
Continue Learning:
- Object types and interfaces
- Tuples and enums
- Type aliases and type composition
- Advanced type features (generics, utility types)
Congratulations! You've mastered TypeScript's fundamental types. These building blocks are the foundation for everything else in TypeScript. Practice using them in real projects to solidify your understanding.
Questions about TypeScript types? Struggling with type errors? Leave a comment below and let's work through it together!