DEV Community

Cover image for Object.freeze() Goes Hard ๐Ÿฅถโ„๏ธ
Matt Lewandowski
Matt Lewandowski

Posted on

Object.freeze() Goes Hard ๐Ÿฅถโ„๏ธ

The title says it all. Let's talk about one of JavaScript's most underrated features: Object.freeze(). This powerhouse of immutability isn't just another method - it's your secret weapon for writing safer, more predictable code โœจ.

I'll be honest, when I first discovered Object.freeze(), I mostly ignored it. "Just don't mutate your objects," I thought. But as my applications grew more complex, I started to see its true value. Now, it's an essential part of my toolkit.

7 Game-Changing Use Cases for Object.freeze() ๐ŸงŠ

Let me show you why Object.freeze() is absolutely essential and how it can level up your JavaScript game. Let's dive into some real-world examples! ๐Ÿฅถ

1: Constants That Are Actually Constant

We've all been there - creating "constant" objects that end up getting mutated somewhere in our codebase.

interface Config {
  api: string;
  timeout: number;
  retries: number;
}

// Without freeze - supposedly "constant" but can be modified
const CONFIG: Config = {
  api: "https://api.example.com",
  timeout: 5000,
  retries: 3
};

// Oops! This works even though it's "constant"
CONFIG.timeout = 1000;

// With freeze - truly immutable
const FROZEN_CONFIG: Config = Object.freeze({
  api: "https://api.example.com",
  timeout: 5000,
  retries: 3
});

// This throws an error in strict mode! ๐ŸŽ‰
FROZEN_CONFIG.timeout = 1000;
Enter fullscreen mode Exit fullscreen mode

Now your configuration is actually immutable. No more debugging mysterious config changes.

2: Protecting Default State

This one's a game-changer for state management, especially in Redux or similar solutions.

interface AppState {
  user: {
    name: string;
    preferences: {
      theme: 'light' | 'dark';
      notifications: boolean;
    };
  };
  settings: {
    language: string;
  };
}

const initialState: AppState = Object.freeze({
  user: {
    name: '',
    preferences: {
      theme: 'light',
      notifications: true
    }
  },
  settings: {
    language: 'en'
  }
});

// Now you can't accidentally modify your initial state!
// This will throw in strict mode
initialState.user.preferences.theme = 'dark';
Enter fullscreen mode Exit fullscreen mode

3: Enum-like Objects That Can't Be Modified

JavaScript doesn't have true enums, but with Object.freeze() we can get pretty close:

const HttpStatus = Object.freeze({
  OK: 200,
  CREATED: 201,
  BAD_REQUEST: 400,
  UNAUTHORIZED: 401,
  NOT_FOUND: 404,
  INTERNAL_SERVER_ERROR: 500,
  // TypeScript bonus: as const for literal types
} as const);

// This will throw! Your status codes are safe ๐Ÿ”’
HttpStatus.OK = 999;

// TypeScript knows the exact types
type StatusCode = typeof HttpStatus[keyof typeof HttpStatus];
// Type is exactly: 200 | 201 | 400 | 401 | 404 | 500
Enter fullscreen mode Exit fullscreen mode

4: Deep Freezing Objects

Object.freeze() is shallow by default, but we can create a deep freeze utility:

function deepFreeze<T>(obj: T): Readonly<T> {
  // Get all properties, including non-enumerable ones
  const propNames = Object.getOwnPropertyNames(obj);

  // Freeze properties before freezing parent
  propNames.forEach(name => {
    const value = (obj as any)[name];

    if (value && typeof value === 'object') {
      deepFreeze(value);
    }
  });

  return Object.freeze(obj);
}

const complexObject = deepFreeze({
  level1: {
    level2: {
      level3: {
        value: "can't touch this"
      }
    }
  }
});

// This will throw! Deep freezing for the win ๐Ÿ†
complexObject.level1.level2.level3.value = "try to touch this";
Enter fullscreen mode Exit fullscreen mode

5: Protecting Event Handlers

Ever had bugs because an event object was modified after the event? Not anymore!

interface CustomEvent {
  type: string;
  timestamp: number;
  data: any;
}

class EventEmitter {
  private handlers: { [key: string]: Function[] } = {};

  emit(event: CustomEvent) {
    // Freeze the event object to prevent modifications
    const frozenEvent = Object.freeze({ ...event });

    const handlers = this.handlers[event.type] || [];
    handlers.forEach(handler => handler(frozenEvent));
  }

  on(type: string, handler: Function) {
    if (!this.handlers[type]) {
      this.handlers[type] = [];
    }
    this.handlers[type].push(handler);
  }
}
Enter fullscreen mode Exit fullscreen mode

6: Immutable API Responses

Keep your API responses immutable to prevent accidental modifications:

interface ApiResponse<T> {
  data: T;
  metadata: {
    timestamp: number;
    requestId: string;
  };
}

async function fetchData<T>(url: string): Promise<ApiResponse<T>> {
  const response = await fetch(url);
  const json = await response.json();

  // Freeze the response immediately
  return Object.freeze({
    data: json,
    metadata: {
      timestamp: Date.now(),
      requestId: crypto.randomUUID()
    }
  });
}

// Usage
const response = await fetchData('https://api.example.com/data');
// This will throw! Your API response is safe ๐Ÿ›ก๏ธ
response.data = null;
Enter fullscreen mode Exit fullscreen mode

7: Creating Truly Private Properties

While we now have private fields with #, Object.freeze() can help create truly private properties in factory functions:

interface User {
  readonly id: string;
  readonly username: string;
  getInfo(): string;
}

function createUser(username: string): User {
  const privateState = {
    loginAttempts: 0,
    lastLogin: new Date()
  };

  // Public interface is frozen
  return Object.freeze({
    id: crypto.randomUUID(),
    username,
    getInfo() {
      return `${username} (Last login: ${privateState.lastLogin})`;
    }
  });
}

const user = createUser('alice');
// This throws! Can't add properties
user.admin = true;
// This throws! Can't modify existing ones
user.username = 'bob';
Enter fullscreen mode Exit fullscreen mode

Performance Considerations ๐Ÿš€

While Object.freeze() is powerful, it's important to understand its performance implications:

  1. The freezing operation itself has a cost, especially with deepFreeze
  2. Frozen objects can be slightly slower to read from (but the difference is usually negligible)
  3. TypeScript's readonly is compile-time only and has zero runtime cost

Here's a performance-conscious approach:

// Development: Use Object.freeze() for better error catching
const isDev = process.env.NODE_ENV === 'development';

function safeFreeze<T>(obj: T): Readonly<T> {
  return isDev ? Object.freeze(obj) : obj;
}

// Now use safeFreeze everywhere
const config = safeFreeze({
  // your config here
});
Enter fullscreen mode Exit fullscreen mode

TypeScript Magic โœจ

Object.freeze() works beautifully with TypeScript's type system:

// Object.freeze() automatically makes properties readonly
const frozen = Object.freeze({ x: 1, y: 2 });
// TypeScript error: Cannot assign to 'x' because it is a readonly property
frozen.x = 2;

// Works with 'as const' for literal types
const DIRECTIONS = Object.freeze({
  UP: 'UP',
  DOWN: 'DOWN',
  LEFT: 'LEFT',
  RIGHT: 'RIGHT'
} as const);

// Type is exactly: 'UP' | 'DOWN' | 'LEFT' | 'RIGHT'
type Direction = typeof DIRECTIONS[keyof typeof DIRECTIONS];
Enter fullscreen mode Exit fullscreen mode

Conclusion

Object.freeze() might seem simple at first glance, but it's an incredibly powerful tool for writing safer, more maintainable JavaScript. From protecting configuration objects to ensuring immutable state, it's an essential part of modern JavaScript development.

The next time you find yourself reaching for a third-party immutability library, remember that Object.freeze() might be all you need! ๐Ÿฅถโœจ

Oh and one last shameless plug ๐Ÿ˜
If you're looking to run retrospectives or planning poker sessions, check out Kollabe. It's a robust, production-tested platform that's constantly evolving to create the best real-time collaboration experience possible.

Top comments (1)

Collapse
 
abustamam profile image
Rasheed Bustamam

Great writeup! I think TypeScript makes Object.freeze a bit redundant though. You can annotate props as readonly which will cause a type error, and the as const will also work with keyof.

But without TS, Object.freeze can definitely save your butt!