Dominando las Estructuras de Datos: Guía para Entrevistas en JavaScript/TypeScript
In this article
- 1. Arrays (La Base)
- Implementación Manual
- 2. Hash Tables (Objetos / Maps)
- Implementación Manual
- 3. Linked Lists (Listas Enlazadas)
- Implementación Manual
- 4. Pilas (Stacks - LIFO)
- Implementación Manual
- 5. Colas (Queues - FIFO)
- Implementación Manual
- 6. Árboles de Búsqueda Binaria (BST)
- Implementación Manual
- 7. Grafos
- Tipos de Grafos
- Cómo representarlos en código
- 8. Heaps (Mención rápida)
- Hoja de Trucos: Resumen
Continuando con nuestra discusión sobre la notación Big O, el siguiente paso crítico para triunfar en las entrevistas técnicas es dominar las Estructuras de Datos. Para entenderlas de verdad, no deberíamos limitarnos a usar los métodos integrados; deberíamos construirlas desde cero.
En esta guía, implementaremos las estructuras principales, analizaremos sus “trampas” de rendimiento y aprenderemos los consejos de nivel “Senior” que buscan los entrevistadores.

1. Arrays (La Base)
En lenguajes de bajo nivel, los arrays son bloques contiguos de memoria. En JavaScript, son mucho más flexibles, pero esa flexibilidad tiene un “coste” oculto.
Implementación Manual
class MiArray<T> { private length: number = 0; private data: { [key: number]: T } = {};
get(index: number): T { return this.data[index]; } // O(1)
push(item: T): number { this.data[this.length] = item; this.length++; return this.length; // O(1) }
pop(): T | undefined { const ultimoItem = this.data[this.length - 1]; delete this.data[this.length - 1]; this.length--; return ultimoItem; // O(1) }
delete(index: number): T | undefined { const item = this.data[index]; this.shiftItems(index); // O(n) return item; }
private shiftItems(index: number) { for (let i = index; i < this.length - 1; i++) { this.data[i] = this.data[i + 1]; } delete this.data[this.length - 1]; this.length--; }}[!IMPORTANT] La Trampa de los Strings: En JS, los Strings son inmutables. Aunque
str[0]parece el acceso a un array, no puedes cambiarlo. Concatenar strings en un bucle (str += next) crea un nuevo string cada vez, lo que lleva a una complejidad . Consejo: Usa siempre un array y.join('')para manipulaciones pesadas de texto.
2. Hash Tables (Objetos / Maps)
Las Hash Tables almacenan pares clave-valor. Utilizan una Función Hash para asignar una clave a un índice específico en un array interno.
Implementación Manual
class MiHashTable { private data: any[][]; constructor(size: number) { this.data = new Array(size); }
private _hash(key: string): number { let hash = 0; for (let i = 0; i < key.length; i++) { hash = (hash + key.charCodeAt(i) * i) % this.data.length; } return hash; }
set(key: string, value: any) { const direccion = this._hash(key); if (!this.data[direccion]) this.data[direccion] = []; this.data[direccion].push([key, value]); // Manejo de colisiones mediante encadenamiento }
get(key: string) { const direccion = this._hash(key); const bucket = this.data[direccion]; if (bucket) { for (const par of bucket) { if (par[0] === key) return par[1]; // O(1) promedio, O(n) peor caso } } }}3. Linked Lists (Listas Enlazadas)
Una colección de nodos donde cada uno apunta al siguiente. A diferencia de los arrays, los nodos no están necesariamente contiguos en memoria.
Implementación Manual
class NodoLista<T> { constructor(public valor: T, public siguiente: NodoLista<T> | null = null) {}}
class MiListaEnlazada<T> { cabeza: NodoLista<T> | null = null; cola: NodoLista<T> | null = null; longitud: number = 0;
append(valor: T) { const nuevoNodo = new NodoLista(valor); if (!this.cabeza) { this.cabeza = nuevoNodo; this.cola = nuevoNodo; } else { this.cola!.siguiente = nuevoNodo; this.cola = nuevoNodo; } this.longitud++; // O(1) gracias al puntero cola }
prepend(valor: T) { this.cabeza = new NodoLista(valor, this.cabeza); if (!this.cola) this.cola = this.cabeza; this.longitud++; // O(1) }
insert(index: number, valor: T) { if (index >= this.longitud) return this.append(valor); if (index === 0) return this.prepend(valor);
const lider = this._getAt(index - 1); const nuevoNodo = new NodoLista(valor, lider!.siguiente); lider!.siguiente = nuevoNodo; this.longitud++; // O(n) búsqueda + O(1) inserción }
private _getAt(index: number) { let actual = this.cabeza; for (let i = 0; i < index; i++) actual = actual!.siguiente; return actual; }}4. Pilas (Stacks - LIFO)
Una Pila sigue el principio Last-In, First-Out (Último en Entrar, Primero en Salir). Imagina una pila de libros: añades libros arriba y los quitas de arriba. En software, así es como el Call Stack gestiona la ejecución de funciones.
Implementación Manual
class Pila<T> { private items: T[] = [];
push(item: T) { this.items.push(item); // O(1) }
pop(): T | undefined { return this.items.pop(); // O(1) }
peek(): T | undefined { return this.items[this.items.length - 1]; // O(1) }
isEmpty(): boolean { return this.items.length === 0; }}5. Colas (Queues - FIFO)
Una Cola sigue el principio First-In, First-Out (Primero en Entrar, Primero en Salir). Como una fila en el supermercado, el primero en llegar es el primero en ser atendido. Esto es fundamental para la cola de tareas del Event Loop en JavaScript.
Implementación Manual
Usar Array.shift() para dequeue es porque todos los demás elementos deben moverse. Para un real, usamos una Lista Enlazada.
class Cola<T> { private lista = new MiListaEnlazada<T>();
enqueue(item: T) { this.lista.append(item); // O(1) }
dequeue(): T | null { if (this.lista.longitud === 0) return null; const valor = this.lista.cabeza!.valor; this.lista.cabeza = this.lista.cabeza!.siguiente; this.lista.longitud--; if (this.lista.longitud === 0) this.lista.cola = null; return valor; // O(1) }
peek(): T | null { return this.lista.cabeza ? this.lista.cabeza.valor : null; // O(1) }}6. Árboles de Búsqueda Binaria (BST)
Los árboles son estructuras de datos jerárquicas. Un Árbol de Búsqueda Binaria (BST) es un tipo específico donde para cualquier nodo dado:
- Todos los nodos en su subárbol izquierdo tienen un valor menor.
- Todos los nodos en su subárbol derecho tienen un valor mayor.
Implementación Manual
class NodoArbol<T> { constructor( public valor: T, public izquierdo: NodoArbol<T> | null = null, public derecho: NodoArbol<T> | null = null ) {}}
class BST<T> { raiz: NodoArbol<T> | null = null;
// Añadir un elemento manteniendo la propiedad del BST add(valor: T) { const nuevoNodo = new NodoArbol(valor); if (!this.raiz) { this.raiz = nuevoNodo; return; }
let actual = this.raiz; while (true) { if (valor < actual.valor) { if (!actual.izquierdo) { actual.izquierdo = nuevoNodo; return; } actual = actual.izquierdo; } else { if (!actual.derecho) { actual.derecho = nuevoNodo; return; } actual = actual.derecho; } } }
// Buscar un elemento: O(log n) en promedio find(valor: T): NodoArbol<T> | null { let actual = this.raiz; while (actual) { if (valor === actual.valor) return actual; actual = valor < actual.valor ? actual.izquierdo : actual.derecho; } return null; }}[!WARNING] Problema de Rendimiento: Si insertas elementos en orden (ej. 1, 2, 3, 4), el árbol se convierte en una sola línea (desequilibrado). La complejidad se degrada de a . Los Árboles Auto-equilibrados (como AVL o Red-Black) solucionan esto rotando los nodos para mantener la altura mínima.
7. Grafos
Los Grafos representan nodos (vértices) y las conexiones (aristas) entre ellos. Están en todas partes: tu red social, internet y en los mapas.
Tipos de Grafos
- Dirigidos vs. No Dirigidos: En un grafo dirigido (como Twitter), seguir a alguien es unidireccional. En uno no dirigido (como una carretera), puedes ir en ambos sentidos.
- Ponderados vs. No Ponderados: Los grafos ponderados (como Google Maps) tienen “costes” o distancias en cada arista.
Cómo representarlos en código
Imagina tres nodos (0, 1, 2) conectados en un triángulo.
1. Lista de Aristas (Edge List)
Un simple array que muestra las conexiones.
const grafo = [[0, 1], [1, 2], [2, 0]];2. Lista de Adyacencia (Adjacency List)
Un mapeo de cada nodo a sus vecinos. Esta es la implementación más común en entrevistas porque es eficiente en memoria.
const listaAdyacencia = { 0: [1, 2], 1: [0, 2], 2: [0, 1]};3. Matriz de Adyacencia (Adjacency Matrix)
Una cuadrícula donde 1 indica una conexión y 0 la ausencia de ella.
const matriz = [ [0, 1, 1], // El Nodo 0 está conectado con el 1 y el 2 [1, 0, 1], // El Nodo 1 está conectado con el 0 y el 2 [1, 1, 0] // El Nodo 2 está conectado con el 0 y el 1];8. Heaps (Mención rápida)
Un Binary Heap es un árbol especial donde la raíz es siempre el máximo (Max-Heap) o el mínimo (Min-Heap). Se utilizan para implementar Colas de Prioridad, esenciales para algoritmos como el de Dijkstra.
Hoja de Trucos: Resumen
Para más detalles, consulta nuestra guía sobre la Notación Big O.
| Estructura | Acceso | Búsqueda | Inserción | Uso en el Mundo Real |
|---|---|---|---|---|
| Array | Listas simples / buffers | |||
| Hash Map | Cachés / Búsquedas rápidas | |||
| Lista Enlazada | Sistemas de Deshacer/Rehacer | |||
| Pila | Llamadas a funciones / Historial | |||
| Cola | Programación de tareas | |||
| BST | Datos jerárquicos (DOM) | |||
| Grafo | Redes sociales / GPS |
Dominar estas implementaciones manuales y comprender sus compromisos es lo que diferencia a los desarrolladores junior de los ingenieros senior. No te limites a usar las herramientas: aprende cómo están construidas.