GUIDES
Production-ready code examples for common use cases. Learn by doing.
Back to README • API Docs • See INTEGRATIONS
Table of Contents
Design Patterns
Pattern 1: Iterator Pattern (Zero Conversions)
import { RedBlackTree } from 'data-structure-typed';
// Problem: Need to work with sorted data in multiple ways
const scores = [95, 23, 67, 89, 12, 45];
// Solution: Use iterator protocol
const tree = new RedBlackTree(scores);
// Method 1: Spread operator
const sorted = [...tree.keys()];
console.log(sorted); // [12, 23, 45, 67, 89, 95]
// Method 2: for...of loop
for (const [score] of tree) {
console.log(score);
}
// Method 3: Destructuring
const [min, ...rest] = tree.keys();
// Method 4: Set constructor
const unique = new Set(tree.keys());
// Method 5: Array.from()
const array = Array.from(tree.keys());
// All work automatically - zero conversions!
Pattern 2: Method Chaining (Stay on Structure)
import { RedBlackTree } from 'data-structure-typed';
const tree = new RedBlackTree([
[1, { name: 'Alice', score: 95 }],
[2, { name: 'Bob', score: 45 }],
[3, { name: 'Charlie', score: 87 }],
]);
// Chain operations - structure maintained throughout
const result = tree
.filter((student, id) => (student?.score ?? 0) >= 50)
.map((student, id) => ([id, {
id,
name: student?.name,
passed: (student?.score ?? 0) >= 50
}]))
.reduce((summary, student) => {
summary.count++;
summary.names.push(student?.name ?? '');
return summary;
}, { count: 0, names: [] as string[] });
console.log(result);
// { count: 2, names: ['Alice', 'Charlie'] }
Pattern 3: Seamless Structure Conversion
import { RedBlackTree, Deque, MaxHeap } from 'data-structure-typed';
const data = [64, 34, 25, 12, 22, 11, 90];
// Convert between structures instantly
const tree = new RedBlackTree(data);
const sorted = [...tree.keys()]; // [11, 12, 22, 25, 34, 64, 90]
const heap = new MaxHeap(sorted);
const byPriority = [...heap]; // [90, 64, 34, ...]
const deque = new Deque(byPriority);
const processed = deque.shift(); // Remove first - O(1)!
// No intermediate conversions, structure preserved when possible
Pattern 4: Conditional Logic with Types
import { RedBlackTree } from 'data-structure-typed';
interface Product {
id: string;
name: string;
price: number;
}
class PriceIndex {
private index = new RedBlackTree<number, Product>([], {isMapMode: false});
addProduct(product: Product) {
this.index.set(product.price, product);
}
getByPriceRange(min: number, max: number) {
return this.index.rangeSearch([min, max], node => node.value)
}
getAffordable(budget: number) {
return this.index.rangeSearch([0, budget], node => node.value)
}
}
const index = new PriceIndex();
index.addProduct({id: '1', name: 'Laptop', price: 999});
index.addProduct({id: '2', name: 'Mouse', price: 25});
const affordable = index.getAffordable(100);
Pattern 5: High-Throughput Insertions with Hints (RedBlackTree)
If you insert keys in sorted or nearly-sorted order (timestamps, auto-increment IDs, etc.),
setWithHintNode() can avoid repeated full root-to-leaf searches.
import { RedBlackTree } from 'data-structure-typed';
import type { RedBlackTreeNode } from 'data-structure-typed';
const tree = new RedBlackTree<number, number>([], { isMapMode: true });
let hint: RedBlackTreeNode<number, number> | undefined;
for (let i = 0; i < 1_000_000; i++) {
hint = tree.setWithHintNode(i, i, hint);
}
// tree.size === 1_000_000
Notes:
- Pass the last returned node as the hint.
- If the hint is not valid for the key, the implementation safely falls back to normal
set().
Real-World Examples
Example 1: LRU Cache
import { DoublyLinkedList } from 'data-structure-typed';
class LRUCache<K, V> {
private cache = new Map<K, { value: V; node: any }>();
private order = new DoublyLinkedList<K>();
private readonly capacity: number;
constructor(capacity: number) {
this.capacity = capacity;
}
get(key: K): V | null {
if (!this.cache.has(key)) return null;
const {value, node} = this.cache.get(key)!;
// Move to end (most recently used)
this.order.delete(node);
const newNode = this.order.push(key);
this.cache.set(key, {value, node: newNode});
return value;
}
set(key: K, value: V): void {
if (this.cache.has(key)) {
this.get(key); // Mark as recently used
this.cache.set(key, {value, node: this.cache.get(key)!.node});
return;
}
if (this.cache.size >= this.capacity) {
// Evict least recently used
const lru = this.order.shift();
if (lru) this.cache.delete(lru);
}
const node = this.order.push(key);
this.cache.set(key, {value, node});
}
}
// Usage
const cache = new LRUCache<string, string>(3);
cache.set('a', 'value1');
cache.set('b', 'value2');
cache.set('c', 'value3');
console.log(cache.get('a')); // 'value1', 'a' is now most recent
cache.set('d', 'value4'); // Evicts 'b' (least recent)
Example 2: Real-Time Leaderboard
import { RedBlackTree } from 'data-structure-typed';
// TODO
interface Player {
id: string;
name: string;
score: number;
}
class Leaderboard {
private scores = new RedBlackTree<number, Player>(
(a, b) => b - a // Descending order
);
private players = new Map<string, number>(); // playerId → currentScore
updateScore(player: Player): void {
// Remove old score if exists
if (this.players.has(player.id)) {
const oldScore = this.players.get(player.id)!;
this.scores.delete(oldScore);
}
// Add new score
this.scores.set(player.score, player);
this.players.set(player.id, player.score);
}
// O(k log n) — walk from highest score, no array copy
getTopN(n: number): Player[] {
const result: Player[] = [];
let key = this.scores.getLeftMost(); // highest in desc tree
while (key !== undefined && result.length < n) {
const player = this.scores.get(key);
if (player) result.push(player);
key = this.scores.higher(key); // next in tree order
}
return result;
}
// O(log n + k) — count entries above the target score
getRank(playerId: string): number {
if (!this.players.has(playerId)) return -1;
const score = this.players.get(playerId)!;
let rank = 1;
let key = this.scores.getLeftMost();
while (key !== undefined && key > score) {
rank++;
key = this.scores.higher(key);
}
return rank;
}
// O(log n + k) — navigate to target player, then walk ±range
getAroundMe(playerId: string, range: number): Player[] {
if (!this.players.has(playerId)) return [];
const myScore = this.players.get(playerId)!;
const result: Player[] = [];
// Collect `range` players above me
const above: Player[] = [];
let key = this.scores.lower(myScore);
for (let i = 0; i < range && key !== undefined; i++) {
const p = this.scores.get(key);
if (p) above.unshift(p);
key = this.scores.lower(key);
}
// Me + `range` players below me
result.push(...above);
const me = this.scores.get(myScore);
if (me) result.push(me);
key = this.scores.higher(myScore);
for (let i = 0; i < range && key !== undefined; i++) {
const p = this.scores.get(key);
if (p) result.push(p);
key = this.scores.higher(key);
}
return result;
}
}
// Usage
const lb = new Leaderboard();
lb.updateScore({ id: '1', name: 'Alice', score: 1000 });
lb.updateScore({ id: '2', name: 'Bob', score: 900 });
lb.updateScore({ id: '3', name: 'Charlie', score: 950 });
console.log(lb.getTopN(2)); // Top 2 players
console.log(lb.getRank('2')); // Bob's rank
console.log(lb.getAroundMe('2', 1)); // Players around Bob
Example 3: Message Queue with Priorities
import { Deque, MaxPriorityQueue } from 'data-structure-typed';
interface Message {
id: string;
content: string;
priority: number;
timestamp: number;
}
class PriorityMessageQueue {
private urgent = new Deque<Message>(); // Priority >= 8
private normal = new Deque<Message>(); // Priority 4-7
private low = new Deque<Message>(); // Priority < 4
enqueue(message: Message): void {
if (message.priority >= 8) {
this.urgent.push(message);
} else if (message.priority >= 4) {
this.normal.push(message);
} else {
this.low.push(message);
}
}
dequeue(): Message | null {
// Serve urgent first, then normal, then low
return (
this.urgent.shift() ||
this.normal.shift() ||
this.low.shift() ||
null
);
}
length(): number {
return this.urgent.length + this.normal.length + this.low.length;
}
}
// Usage
const queue = new PriorityMessageQueue();
queue.enqueue({ id: '1', content: 'Normal task', priority: 5, timestamp: Date.now() });
queue.enqueue({ id: '2', content: 'Urgent task', priority: 9, timestamp: Date.now() });
queue.enqueue({ id: '3', content: 'Low task', priority: 1, timestamp: Date.now() });
while (queue.length() > 0) {
const msg = queue.dequeue();
console.log(msg?.id); // 2, 1, 3 (urgent first)
}
Example 4: Task Scheduler
interface Task {
id: string;
action: () => Promise<void>;
priority: number;
scheduledTime: number;
}
class TaskScheduler {
private queue = new MaxPriorityQueue<Task>([], {
comparator: (a, b) => a.priority - b.priority
});
private running = false;
scheduleTask(task: Task): void {
this.queue.add(task);
if (!this.running) void this.start();
}
private async start(): Promise<void> {
this.running = true;
while (!this.queue.isEmpty()) {
const task = this.queue.poll();
if (!task) break;
try {
console.log(`Executing task ${task.id}`);
await task.action();
} catch (error) {
console.error(`Task ${task.id} failed:`, error);
}
}
this.running = false;
}
}
// Usage
const scheduler = new TaskScheduler();
scheduler.scheduleTask({
id: '1',
action: async () => console.log('High priority'),
priority: 10,
scheduledTime: Date.now()
});
scheduler.scheduleTask({
id: '2',
action: async () => console.log('Low priority'),
priority: 1,
scheduledTime: Date.now()
});
Example 5: Search Index with Autocomplete
import {Trie} from 'data-structure-typed';
class SearchIndex {
private trie = new Trie();
private documents = new Map<string, string[]>();
indexDocument(docId: string, content: string): void {
const words = content.toLowerCase().split(/\s+/);
for (const word of words) {
// Insert word into trie
if (!this.trie.has(word)) {
this.trie.add(word);
}
// Track which documents contain this word
if (!this.documents.has(word)) {
this.documents.set(word, []);
}
this.documents.get(word)!.push(docId);
}
}
autocomplete(prefix: string): string[] {
const words = this.trie.getWords(prefix);
return words.filter((_word, index) => index < 10); // Top 10
}
search(query: string): string[] {
const word = query.toLowerCase();
return this.documents.get(word) || [];
}
}
// Usage
const index = new SearchIndex();
index.indexDocument('doc1', 'apple application');
index.indexDocument('doc2', 'app store');
console.log(index.autocomplete('app')); // ['apple', 'application', 'app']
console.log(index.search('apple')); // ['doc1']
Common Mistakes
Mistake 1: Forgetting to Convert Back
// ❌ Wrong
const tree = new RedBlackTree([5, 2, 8]);
const doubled = tree.map((_v, k) => [k * 2, undefined]); // Still a tree!
JSON.stringify(doubled); // Error or wrong output
// ✅ Right
const tree = new RedBlackTree([5, 2, 8]);
const doubled = tree.map((_v, k) => [k * 2, undefined]);
JSON.stringify([...doubled.keys()]); // [4, 10, 16]
Mistake 2: Mutating During Iteration
// ❌ Wrong
const tree = new RedBlackTree([1, 2, 3, 4, 5, 6]);
for (const [key, _value] of tree) {
if (key % 2 === 0) tree.delete(key); // Avoid this!
}
// ✅ Right
const tree = new RedBlackTree([1, 2, 3, 4, 5, 6]);
const toDelete = [];
for (const [key, _value] of tree) {
if (key % 2 === 0) toDelete.push(key);
}
toDelete.forEach(v => tree.delete(v));
// ✅ Perfect
const tree = new RedBlackTree([1, 2, 3, 4, 5, 6]);
tree.deleteWhere((node) => node.key % 2 === 0);
Mistake 3: Wrong Data Structure Choice
// ❌ Wrong - using Array for sorted data with updates
const scores = [];
function addScore(score) {
scores.push(score);
scores.sort((a, b) => b - a); // O(n log n) every time!
}
// ✅ Right - using RedBlackTree
const scores = new RedBlackTree();
function addScore(score) {
scores.set(score, true); // O(log n), auto-sorted
}
Best Practices
1. Choose Structure Early
// ❌ Bad: Convert later
function processScores(scores: number[]) {
const sorted = [...scores].sort((a, b) => b - a);
return new RedBlackTree(sorted).filter(x => x > 50); // Redundant
}
// ✅ Good: Decide upfront
function processScores(scores: number[]) {
const tree = new RedBlackTree(scores); // Already sorted
return tree.filter(x => x > 50).map((_v,k) => [k * 1.1, undefined]);
}
2. Batch Operations When Possible
// ❌ Bad: Individual inserts
const tree = new RedBlackTree();
for (const item of largeDataset) {
tree.set(item.id, item); // Rebalances each time
}
// ✅ Good: Bulk insert
const tree = new RedBlackTree(largeDataset);
3. Use Type Safety
// ❌ Bad: No type checking
const tree = new RedBlackTree();
tree.set(1, { id: 1, name: 'Alice' });
// ✅ Good: Full type inference
const tree = new RedBlackTree<number, User>();
tree.set(1, { id: 1, name: 'Alice' });
4. Chain When Possible
// ❌ Bad: Convert unnecessarily
const result = [...tree]
.filter(x => x > 10)
.map(x => x * 2)
.reduce((sum, x) => sum + x, 0);
// ✅ Good: Stay on structure
const result = tree
.filter(v => v > 10)
.map(v => [(v ?? 0) * 2, undefined])
.reduce((sum, v) => sum + (v ?? 0), 0);
Need more? Check INTEGRATIONS.md for framework examples.
Performance questions? See PERFORMANCE.md.