Static Type Checking
TypeScript performs type checking at compile time, catching errors before code runs. This prevents runtime type errors and provides immediate feedback in development environments.
About 1007 wordsAbout 13 min
2025-08-05
Note
Type safety and immutability are core concepts in TypeScript that help prevent bugs and make code more predictable. TypeScript provides various tools and patterns to enforce type safety and ensure data integrity throughout applications.
Type safety ensures that variables are used consistently with their declared types, preventing many common programming errors.
Static Type Checking
TypeScript performs type checking at compile time, catching errors before code runs. This prevents runtime type errors and provides immediate feedback in development environments.
Type Inference
TypeScript can automatically infer types from context, reducing the need for explicit type annotations while still maintaining type safety.
NoImplicitAny
The noImplicitAny compiler flag prevents implicit any types, forcing developers to be explicit about types when the compiler cannot infer them.
Immutability prevents accidental modification of data, making applications more predictable and easier to reason about.
Const Declarations
The const keyword prevents reassignment of variables, but doesn't protect object properties from modification.
const user = {
name: "Alice",
age: 30
};
user = { name: "Bob", age: 25 }; // Error: Cannot assign to 'user' because it is a constant
user.name = "Bob"; // Allowed: object properties can still be modifiedStep 1
Readonly Properties
Use readonly in interfaces or the Readonly<T> utility type to prevent property modifications.
interface ImmutableUser {
readonly id: number;
readonly name: string;
readonly email: string;
}
const user: ImmutableUser = {
id: 1,
name: "Alice",
email: "alice@example.com"
};
user.name = "Bob"; // Error: Cannot assign to 'name' because it is read-onlyStep 2
Const Assertions
Use as const to create deeply immutable objects and narrow types to their literal values.
const config = {
apiUrl: "https://api.example.com",
timeout: 5000,
retries: 3
} as const;
// Type is now: { readonly apiUrl: "https://api.example.com"; readonly timeout: 5000; readonly retries: 3; }
config.apiUrl = "https://new.api.com"; // Error: Cannot assign to 'apiUrl' because it is read-onlyStep 3
Following type safety best practices helps create more robust and maintainable applications.
Avoid the 'any' Type The any type bypasses type checking and should be avoided whenever possible.
// Bad practice
function processData(data: any): any {
return data.process();
}
// Good practice
function processData<T extends { process(): unknown }>(data: T): unknown {
return data.process();
}
// Better practice with specific return type
interface Processable {
process(): string;
}
function processData(data: Processable): string {
return data.process();
}Use Type Guards Type guards help TypeScript understand types in conditional blocks.
function isString(value: unknown): value is string {
return typeof value === "string";
}
function processValue(value: unknown): string {
if (isString(value)) {
// TypeScript knows value is string here
return value.toUpperCase();
}
if (typeof value === "number") {
// TypeScript knows value is number here
return value.toFixed(2);
}
return String(value);
}Enable Strict Compiler Options Use strict compiler options to catch more potential errors.
{
"compilerOptions": {
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"strictBindCallApply": true,
"strictPropertyInitialization": true,
"noImplicitThis": true,
"alwaysStrict": true
}
}Immutable patterns help prevent side effects and make applications more predictable.
// Immutable array operations
interface ImmutableArray<T> {
readonly length: number;
includes(searchElement: T): boolean;
indexOf(searchElement: T): number;
join(separator?: string): string;
slice(start?: number, end?: number): T[];
}
// Immutable object creation patterns
interface User {
id: number;
name: string;
email: string;
}
function updateUser(user: User, updates: Partial<User>): User {
return {
...user,
...updates
};
}
const originalUser: User = {
id: 1,
name: "Alice",
email: "alice@example.com"
};
const updatedUser = updateUser(originalUser, {
name: "Alice Smith"
});
// Original user remains unchanged
console.log(originalUser.name); // "Alice"
console.log(updatedUser.name); // "Alice Smith"
// Immutable state management pattern
interface State {
readonly users: ReadonlyArray<User>;
readonly loading: boolean;
readonly error: string | null;
}
function reducer(state: State, action: ActionType): State {
switch (action.type) {
case "SET_LOADING":
return { ...state, loading: action.payload };
case "SET_USERS":
return { ...state, users: action.payload, loading: false };
case "SET_ERROR":
return { ...state, error: action.payload, loading: false };
default:
return state;
}
}Real-world examples of type safety patterns and immutability:
// Type-safe API client
class ApiClient {
constructor(
private readonly baseUrl: string,
private readonly timeout: number = 5000
) {}
async get<T>(endpoint: string): Promise<T> {
const response = await fetch(`${this.baseUrl}${endpoint}`, {
method: 'GET',
signal: AbortSignal.timeout(this.timeout)
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.json() as Promise<T>;
}
async post<T, U>(endpoint: string, data: U): Promise<T> {
const response = await fetch(`${this.baseUrl}${endpoint}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
signal: AbortSignal.timeout(this.timeout)
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.json() as Promise<T>;
}
}
// Type-safe event system
interface TypedEventTarget<TEventMap extends Record<string, any>> {
addEventListener<K extends keyof TEventMap>(
type: K,
listener: (event: TEventMap[K]) => void
): void;
removeEventListener<K extends keyof TEventMap>(
type: K,
listener: (event: TEventMap[K]) => void
): void;
dispatchEvent<K extends keyof TEventMap>(event: TEventMap[K]): void;
}
interface AppEvents {
userLoggedIn: { userId: number; timestamp: Date };
userLoggedOut: { userId: number; timestamp: Date };
dataLoaded: { data: any; source: string };
}
// Type-safe configuration builder
class ConfigBuilder<T> {
private config: Partial<T> = {};
set<K extends keyof T>(key: K, value: T[K]): this {
this.config[key] = value;
return this;
}
build(): T {
if (!this.isConfigComplete(this.config)) {
throw new Error('Configuration is incomplete');
}
return this.config as T;
}
private isConfigComplete(config: Partial<T>): config is T {
// Implementation depends on specific requirements
return Object.keys(config).length > 0;
}
}A programming language feature where types are enforced at compile time, preventing type errors and ensuring that operations are performed on compatible data types.
The property of an object that cannot be modified after it is created, preventing side effects and making code more predictable and easier to reason about.
A TypeScript feature using as const that creates immutable objects and narrows types to their literal values, providing stronger type guarantees.
A TypeScript expression that performs a runtime check to determine the type of a value, allowing the compiler to narrow the type within conditional blocks for better type safety.
013dd-feat: type safety for tson Copyright Ownership:WARREN Y.F. LONG
License under:Attribution-NonCommercial-NoDerivatives 4.0 International (CC-BY-NC-ND-4.0)