DEV Community

Cover image for Set Is Just Built Different 😤💪
Matt Lewandowski
Matt Lewandowski

Posted on

Set Is Just Built Different 😤💪

The title says it all 😤. Let's talk about JavaScript's most slept-on data structure: Set. Everyone's out here using arrays and objects, but Set? Set is just built different ✨.

I'll be real with you - I used to skip over Set entirely. "Just use an array," I thought. Or "objects are faster for lookups." But the more I worked with large-scale applications like Kollabe, the more I realized: Set is the secret sauce that combines the best of both worlds.

7 Times Set Proves It's Built Different 😤

1: Lightning Fast Lookups AND Clean Syntax

Sets give you object-speed lookups with array-like syntax. It's literally the best of both worlds:

// Arrays: Clean syntax but slow lookups
const arr = [1, 2, 3];
const hasThree = arr.includes(3); // O(n) 🐌

// Objects: Fast lookups but messy syntax
const obj = { 1: true, 2: true, 3: true };
const hasThreeObj = obj[3] === true; // O(1) but 🤮

// Set: Clean AND fast 😎
const set = new Set([1, 2, 3]);
const hasThreeSet = set.has(3); // O(1) AND clean! 🚀
Enter fullscreen mode Exit fullscreen mode

2: Automatic Deduplication That Just Works

Ever had to remove duplicates from an array? Set makes it trivial:

interface User {
  id: number;
  name: string;
}

const users: User[] = [
  { id: 1, name: 'Alice' },
  { id: 2, name: 'Bob' },
  { id: 1, name: 'Alice' }, // Duplicate
];

// The old way: 🤔
const uniqueByReduce = users.reduce((acc, user) => {
  if (!acc.some(u => u.id === user.id)) {
    acc.push(user);
  }
  return acc;
}, [] as User[]);

// The Set way: 😎
const uniqueUsers = [
  ...new Set(users.map(u => JSON.stringify(u)))
].map(u => JSON.parse(u));

// Even better with a custom serializer:
const uniqueById = [...new Set(users.map(u => u.id))]
  .map(id => users.find(u => u.id === id)!);
Enter fullscreen mode Exit fullscreen mode

3: Set Operations That Make Sense

Want to find common elements or differences between collections? Set operations are actually readable:

const backend = new Set(['alice', 'bob', 'charlie']);
const frontend = new Set(['bob', 'charlie', 'dave']);

// Intersection (users who do both)
const fullstack = new Set(
  [...backend].filter(dev => frontend.has(dev))
);

// Difference (backend-only devs)
const backendOnly = new Set(
  [...backend].filter(dev => !frontend.has(dev))
);

// Union (all devs)
const allDevs = new Set([...backend, ...frontend]);

console.log([...fullstack]); // ['bob', 'charlie']
console.log([...backendOnly]); // ['alice']
console.log([...allDevs]); // ['alice', 'bob', 'charlie', 'dave']
Enter fullscreen mode Exit fullscreen mode

4: Type-Safe Enums That Actually Work

TypeScript enums are... controversial. But Sets can give you type-safe, runtime-safe enums:

const HttpMethods = new Set(['GET', 'POST', 'PUT', 'DELETE'] as const);
type HttpMethod = typeof HttpMethods extends Set<infer T> ? T : never;

function makeRequest(method: HttpMethod, url: string) {
  if (!HttpMethods.has(method)) {
    throw new Error('Invalid HTTP method');
  }
  // ... make request
}

// TypeScript error! 🎉
makeRequest('YOLO', '/api');
Enter fullscreen mode Exit fullscreen mode

5: Memory Efficient Large Collections

Sets are more memory efficient than objects for large collections of unique values:

// Let's measure memory usage
function getMemoryUsage(collection: any): number {
  const start = process.memoryUsage().heapUsed;
  // Force garbage collection in Node
  global.gc && global.gc();
  return process.memoryUsage().heapUsed - start;
}

// Generate large dataset
const data = Array.from({ length: 1000000 }, (_, i) => i.toString());

// Compare memory usage
const asObject = data.reduce((acc, val) => ({ ...acc, [val]: true }), {});
const asSet = new Set(data);

console.log('Object memory:', getMemoryUsage(asObject));
console.log('Set memory:', getMemoryUsage(asSet));
// Set uses significantly less memory! 🎉
Enter fullscreen mode Exit fullscreen mode

6: WeakSet: The Memory Management Secret Weapon

Like WeakMap, WeakSet lets you store object references without preventing garbage collection:

const objects = new WeakSet();

class MemoryIntensive {
  constructor() {
    this.largeData = new Array(1000000);
  }
}

{
  const temp = new MemoryIntensive();
  objects.add(temp);
  // temp can be garbage collected even though it's in the WeakSet!
}
// Memory is freed! 🗑️
Enter fullscreen mode Exit fullscreen mode

7: The Ultimate Event Tracker

Sets are perfect for tracking complex states in event systems:

class EventTracker {
  private activeEvents = new Set<string>();
  private completedEvents = new Set<string>();

  startEvent(eventId: string) {
    if (this.activeEvents.has(eventId)) {
      throw new Error('Event already active');
    }
    this.activeEvents.add(eventId);
  }

  completeEvent(eventId: string) {
    if (!this.activeEvents.has(eventId)) {
      throw new Error('Event not active');
    }
    this.activeEvents.delete(eventId);
    this.completedEvents.add(eventId);
  }

  getActiveCount() {
    return this.activeEvents.size;
  }

  getCompletionRate() {
    return this.completedEvents.size / 
      (this.activeEvents.size + this.completedEvents.size);
  }
}
Enter fullscreen mode Exit fullscreen mode

Performance Showdown 🏃‍♂️

Let's be real about performance. Here's how Set stacks up:

const size = 1000000;
const lookupTimes = 10000;

// Setup
const arr = Array.from({ length: size }, (_, i) => i);
const obj = Object.fromEntries(arr.map(n => [n, true]));
const set = new Set(arr);

console.time('Array lookups');
for (let i = 0; i < lookupTimes; i++) {
  arr.includes(size - 1);
}
console.timeEnd('Array lookups');

console.time('Object lookups');
for (let i = 0; i < lookupTimes; i++) {
  obj[size - 1] === true;
}
console.timeEnd('Object lookups');

console.time('Set lookups');
for (let i = 0; i < lookupTimes; i++) {
  set.has(size - 1);
}
console.timeEnd('Set lookups');
Enter fullscreen mode Exit fullscreen mode

The results? Set is consistently faster than Array.includes() and nearly as fast as object property lookups. But unlike objects, it maintains insertion order and has a clean API. Built different indeed 😤.

TypeScript Magic ✨

Set plays incredibly well with TypeScript:

// Literal types with Set
const ValidStates = new Set(['pending', 'active', 'complete'] as const);
type State = typeof ValidStates extends Set<infer T> ? T : never;

// Union types from Set
const NumberSet = new Set([1, 2, 3] as const);
type ValidNumber = typeof NumberSet extends Set<infer T> ? T : never;
// Type is exactly: 1 | 2 | 3

// Generic Set utilities
function isSuperset<T>(set: Set<T>, subset: Set<T>): boolean {
  for (const elem of subset) {
    if (!set.has(elem)) return false;
  }
  return true;
}
Enter fullscreen mode Exit fullscreen mode

When NOT to Use Set

Let's keep it 💯 - Set isn't always the answer:

  1. When you need to serialize data frequently (JSON.stringify)
  2. When you need index-based access (use arrays)
  3. When you need key-value pairs (use Map)
  4. When memory is extremely constrained (arrays might be better)

Conclusion

Set is just built different. It combines the best parts of arrays and objects into something uniquely powerful. From lightning-fast lookups to automatic deduplication, from clean set operations to memory efficiency, Set proves itself as an essential tool in modern JavaScript.

Next time you reach for an array or object, ask yourself: "Could Set handle this better?" The answer might surprise you 😤💪.

P.S. If you're looking for a place to practice these Set operations in a real project, check out Kollabe. It's where I learned just how clutch Set can be for real-time collaboration!

Top comments (0)