Logo

Home

About

Blog

Contact

Guestbook

Portfolio

Privacy

TOS

Click to navigate

  1. Home
  2. Joshua R. Lehman's Blog
  3. Basic Types in TypeScript

Table of contents

  • Share on X
  • Discuss on X

Related Articles

The any, unknown, and never Types
TypeScript
8m
Dec 8, 2025

The any, unknown, and never Types

TypeScript has three special types that handle edge cases: any (the escape hatch), unknown (the type-safe alternative), and never (for impossible values). Understanding when and how to use these types is crucial for writing safe yet flexible TypeScript code. This guide covers the differences between these types, shows you how to work with unknown safely, and demonstrates how never enables exhaustive type checking and prevents runtime errors. Learn to use these types effectively while maintaining type safety.

#TypeScript#Type System+5
Working with Arrays and Tuples
TypeScript
7m
Nov 28, 2025

Working with Arrays and Tuples

Arrays and tuples are essential for working with collections in TypeScript. While arrays hold multiple values of the same type, tuples allow you to work with fixed-length arrays where each position has a specific type. This guide covers array types, readonly patterns, tuple best practices, and practical examples that you'll use in real applications. Learn how to leverage TypeScript's type system for safer, more maintainable collection handling.

#TypeScript#Arrays+5
Introduction to TypeScript: Why Static Typing Matters
TypeScript
7m
Nov 6, 2025

Introduction to TypeScript: Why Static Typing Matters

TypeScript has evolved from a Microsoft experiment to the industry standard for building scalable JavaScript applications. Understanding why static typing matters is the first step in mastering modern web development. This comprehensive introduction explores the benefits, use cases, and real-world impact of adopting TypeScript.

#TypeScript#JavaScript+6

© Joshua R. Lehman

Full Stack Developer

Crafted with passion • Built with modern web technologies

2025 • All rights reserved

Contents

  • Understanding Type Annotations
  • The string Type
    • Basic String Operations
    • Template Literals
    • String vs string
  • The number Type
    • Number Formats
    • Special Number Values
    • BigInt for Large Numbers
  • The boolean Type
    • Truthy and Falsy Values
    • Type Guards with Booleans
  • Arrays in TypeScript
    • Array Type Syntax
    • Array Methods
    • Readonly Arrays
  • The any Type
  • Type Inference
  • Union Types with Primitives
  • Literal Types
  • The null and undefined Types
  • Type Assertions
  • Best Practices for Basic Types
  • Common Mistakes to Avoid
  • Next Steps
TypeScript

Basic Types in TypeScript

November 23, 2025•14 min read
Joshua R. Lehman
Joshua R. Lehman
Author
TypeScript basic types visualization
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 wrapper

Best 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); // NaN

Check 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; // ✓ Works

The 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 boolean

Common 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"; // false

Truthy 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 boolean

Type 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);
// true

Type 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 allowed

Best 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 unknown though)

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: string

Function 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); // ✓ Works

Working 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 valid

Literal 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 assignable

Combining 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/undefined

Strict 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 string

2. 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: number

Common 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!