on order preservation and eliminates prototype chain pollution risks. Understanding these boundaries enables developers to architect state management layers that scale predictably under load.
Core Solution
Implementing production-grade collection patterns requires aligning data semantics with the appropriate primitive. The following implementation demonstrates three architectural patterns: a dynamic session registry, a uniqueness tracker, and a safe metadata attachment layer. All examples use TypeScript to enforce type safety and demonstrate real-world usage.
1. Dynamic Key-Value Registry (Map)
Plain objects coerce keys to strings, which breaks numeric or object-based lookups. Map preserves key types and insertion order, making it ideal for session management, caching, or configuration stores.
interface SessionRecord {
userId: string;
createdAt: number;
lastActivity: number;
}
class SessionRegistry {
private store: Map<string, SessionRecord> = new Map();
public register(sessionId: string, record: SessionRecord): void {
this.store.set(sessionId, record);
}
public retrieve(sessionId: string): SessionRecord | undefined {
return this.store.get(sessionId);
}
public invalidate(sessionId: string): boolean {
return this.store.delete(sessionId);
}
public get activeCount(): number {
return this.store.size;
}
public purgeOlderThan(thresholdMs: number): void {
const now = Date.now();
for (const [id, record] of this.store) {
if (now - record.lastActivity > thresholdMs) {
this.store.delete(id);
}
}
}
}
Architecture Rationale: Map is chosen here because session IDs may eventually include non-string identifiers (e.g., UUIDs, numeric tokens), and the registry requires frequent additions and deletions. The .size property provides O(1) cardinality tracking without manual counting. Insertion order preservation ensures predictable iteration during cleanup routines.
2. Uniqueness & Membership Tracker (Set)
Arrays require O(n) scans for duplicate detection. Set leverages hash-based storage to guarantee uniqueness and provide constant-time membership checks.
type EventId = string;
class EventDeduplicator {
private processed: Set<EventId> = new Set();
public hasProcessed(eventId: EventId): boolean {
return this.processed.has(eventId);
}
public markProcessed(eventId: EventId): void {
this.processed.add(eventId);
}
public getUniqueBatch(ids: EventId[]): EventId[] {
return Array.from(new Set(ids));
}
public clear(): void {
this.processed.clear();
}
}
Architecture Rationale: Set eliminates the need for Array.includes() or filter() operations, which degrade to O(n²) when checking duplicates across large batches. The structure natively enforces uniqueness, reducing validation logic and preventing race conditions in concurrent event processing pipelines.
Attaching metadata to DOM elements or class instances using strong references creates memory leaks when those objects are removed from the application lifecycle. WeakMap holds keys by weak reference, allowing the garbage collector to reclaim entries automatically.
interface ElementMetadata {
observer: MutationObserver;
customData: Record<string, unknown>;
}
class DOMMetadataStore {
private registry: WeakMap<Element, ElementMetadata> = new WeakMap();
public attach(element: Element, metadata: ElementMetadata): void {
this.registry.set(element, metadata);
}
public retrieve(element: Element): ElementMetadata | undefined {
return this.registry.get(element);
}
public detach(element: Element): boolean {
return this.registry.delete(element);
}
}
Architecture Rationale: WeakMap is mandatory here because DOM elements are frequently added and removed from the document tree. Using a standard Map would retain references to detached nodes, causing heap accumulation. The weak reference semantics ensure that when an element is garbage-collected, its associated metadata is automatically reclaimed without explicit teardown.
Pitfall Guide
1. Attempting to Iterate WeakMap or WeakSet
Explanation: Both structures intentionally omit iteration methods (forEach, for...of, .keys()) to prevent exposing garbage-collected references and to maintain internal optimization.
Fix: If iteration is required, maintain a parallel Set of keys for tracking, or switch to Map/Set with explicit lifecycle management.
2. Using Primitives as WeakMap Keys
Explanation: WeakMap only accepts objects as keys. Passing strings, numbers, or symbols throws a TypeError because primitives are not garbage-collected by reference.
Fix: Wrap primitive identifiers in an object literal or use a standard Map when key types include primitives.
3. Assuming Object Insertion Order is Guaranteed
Explanation: While modern engines preserve insertion order for string keys, the ECMAScript specification historically left this behavior implementation-dependent. Relying on it introduces subtle bugs across different runtime environments.
Fix: Use Map when order preservation is a functional requirement. The specification explicitly guarantees insertion order for Map.
4. JSON.stringify on Map or Set
Explanation: Native serialization methods do not recognize Map or Set structures. Calling JSON.stringify() on either returns {} or [], silently dropping all data.
Fix: Convert to arrays before serialization: JSON.stringify(Array.from(map.entries())) or implement a custom toJSON() method.
5. Memory Leaks from Regular Maps Holding DOM References
Explanation: Storing DOM nodes as keys or values in a Map creates strong references that prevent garbage collection, even after the nodes are removed from the document.
Fix: Replace Map with WeakMap for DOM metadata. For value storage, store lightweight identifiers instead of node references.
6. Overusing Set for Primitive Deduplication in Small Arrays
Explanation: While Set is optimal for large datasets, creating a Set for arrays under 50 elements introduces unnecessary allocation overhead compared to a simple filter or includes check.
Fix: Profile collection size. Use Set for dynamic or large-scale deduplication; use array methods for static, small-scale operations.
7. Confusing has() with includes()
Explanation: Set and Map use .has() for membership checks, while arrays use .includes(). Mixing these leads to runtime errors or incorrect logic.
Fix: Standardize on .has() for collection primitives and .includes() for arrays. Document the distinction in team coding standards.
Production Bundle
Action Checklist
Decision Matrix
| Scenario | Recommended Approach | Why | Cost Impact |
|---|
| Static configuration with string keys | Plain Object | Native JSON support; minimal overhead | Low |
| Dynamic session/cache with frequent mutations | Map | O(1) add/delete; insertion order; type-safe keys | Medium |
| Event deduplication or membership tracking | Set | Hash-based uniqueness; O(1) lookups | Low |
| DOM element metadata or class instance state | WeakMap | Automatic GC; prevents memory leaks | Low |
| Object lifecycle tracking without cleanup logic | WeakSet | Weak references; no manual teardown | Low |
| Cross-environment serialization required | Plain Object | Native JSON.stringify compatibility | Low |
Configuration Template
// collection-registry.ts
// Production-ready collection utilities with lifecycle management
export type CollectionType = 'map' | 'set' | 'weakmap' | 'weakset';
export interface CollectionConfig {
type: CollectionType;
maxEntries?: number;
ttlMs?: number;
onEvict?: (key: unknown, value: unknown) => void;
}
export class CollectionFactory {
public static create<T extends CollectionConfig>(config: T): unknown {
switch (config.type) {
case 'map':
return new Map<string, unknown>();
case 'set':
return new Set<unknown>();
case 'weakmap':
return new WeakMap<object, unknown>();
case 'weakset':
return new WeakSet<object>();
default:
throw new Error(`Unsupported collection type: ${config.type}`);
}
}
public static serialize(data: Map<string, unknown> | Set<unknown>): string {
if (data instanceof Map) {
return JSON.stringify(Array.from(data.entries()));
}
if (data instanceof Set) {
return JSON.stringify(Array.from(data));
}
throw new TypeError('Only Map and Set instances can be serialized');
}
}
Quick Start Guide
- Identify the data semantics: Determine if you need key-value pairing, uniqueness enforcement, or metadata attachment. Match the requirement to
Map, Set, WeakMap, or WeakSet.
- Replace legacy object patterns: Swap
{} for Map when keys are dynamic or order matters. Swap arrays for Set when deduplication is required.
- Migrate DOM metadata storage: Locate all instances where DOM nodes are stored in objects or maps. Replace with
WeakMap to enable automatic garbage collection.
- Add serialization guards: Implement explicit conversion routines before logging or transmitting
Map/Set data to prevent silent data loss.
- Validate with profiling: Run heap snapshots in development to confirm that weak references are being collected and that no strong reference cycles remain.