Los strings son probablemente la estructura de datos que más usas sin pensar. Pero, ¿sabes cómo se almacenan en memoria? ¿Por qué no puedes modificarlos directamente? ¿Cuánto cuesta concatenarlos en un loop?
En el artículo anterior vimos cómo funcionan los arrays por debajo: memoria contigua, acceso por índice, operaciones y sus costos. Si aún no lo has leído, te recomiendo empezar por ahí. Hoy toca hablar de sus primos: los strings.
En estos ejemplos usaremos JavaScript como lenguaje principal. Si usas otro lenguaje, te invito a buscar los métodos equivalentes. Los conceptos son los mismos.
Lo que encontrarás en este artículo:Qué es un string y cómo se almacena en memoriaQué significa que sean inmutablesEn qué se parecen y en qué se diferencian de los arraysOperaciones comunes con strings y cuánto cuestan (Big O)Una tabla de complejidad para que la tengas de referencia
1. ¿Qué es un string?
Un string (o cadena de caracteres) es una secuencia de caracteres almacenados de forma contigua en memoria. Suena familiar, ¿verdad? Es básicamente lo que vimos con los arrays, pero en lugar de números o emojis, almacenamos caracteres.
Índice: 0 1 2 3 4
┌───┬───┬───┬───┬───┐
String: │ H │ o │ l │ a │ ! │
└───┴───┴───┴───┴───┘
Para empezar, puedes pensar en un string como un array de caracteres — es un buen modelo mental. Más adelante verás que los motores modernos implementan strings con estructuras más sofisticadas, pero conceptualmente esta idea te va a servir para entender cómo se comportan.
Pero aquí viene el detalle: ¿qué es realmente un "carácter" para la computadora? No es una letra. Es un número. La "H" es el 72, la "o" es el 111. Cada carácter tiene un código numérico asignado por un estándar (como ASCII o Unicode), y un encoding (como UTF-8) convierte ese código a bits. Así que cuando digo "array de caracteres", por debajo es un array de números.
Si quieres entender a fondo cómo funciona eso, lo cubrimos en el artículo de encodings. Ahí explicamos por qué un emoji ocupa 4 bytes y una letra solo 1. 😉
Cada carácter ocupa una posición y tiene un índice, exactamente como en un array. Y al igual que con los arrays, acceder a un carácter por su índice es instantáneo: O(1).
const saludo = "Hola!";
console.log(saludo[0]); // "H"
console.log(saludo[4]); // "!"
Una advertencia importante: este modelo funciona perfectamente con caracteres del alfabeto latino básico, pero se complica con emojis y algunos caracteres especiales. En JavaScript, un emoji como 😀 ocupa dos posiciones en el string, no una:
Esto es por cómo JavaScript representa internamente el texto (UTF-16). Lo vemos a detalle en el artículo de encodings. Por ahora, si trabajas con caracteres simples, el modelo de "array de caracteres" te sirve bien.
2. ¿Por qué los strings son inmutables?
Porque una vez que creas un string, su contenido no puede cambiar. Si necesitas una versión diferente, se crea uno nuevo en memoria. Esto aplica en JavaScript, Python y la mayoría de lenguajes modernos. Aquí es donde los strings empiezan a diferenciarse de los arrays.
¿Qué significa inmutable?
Inmutable quiere decir que algo no puede cambiar después de creado. En programación, cuando decimos que un valor es inmutable, significa que no puedes modificar su contenido directamente. Si necesitas una versión diferente, tienes que crear una nueva.
¿Qué pasa cuando modificas un string?
En lenguajes como JavaScript y Python, los strings son inmutables. Esto quiere decir que cuando "modificas" un string, en realidad se está creando uno nuevo en memoria. El original no se toca.
Veamos un ejemplo:
let palabra = "Hola";
palabra[0] = "J"; // Intentamos cambiar la "H" por "J"
console.log(palabra); // "Hola" — no cambió nada
¿Ves? JavaScript simplemente ignora el intento. No lanza un error, pero tampoco hace el cambio.
Si tu código está en strict mode (lo cual pasa automáticamente dentro de módulos ES y clases), este intento sí lanza un TypeError. Es el comportamiento más común en código moderno.En Python es más explícito:
palabra = "Hola"
palabra[0] = "J" # TypeError: 'str' object does not support item assignment
Python directamente te dice: "no puedes hacer eso".
¿Cuánto cuesta concatenar strings?
Cuando concatenas strings con +=, no estás "agregando" al final como harías con push() en un array. Conceptualmente, se crea un string nuevo que contiene todo el contenido anterior más lo que agregaste.
let resultado = "";
resultado += "Hola"; // Se crea un nuevo string "Hola"
resultado += " "; // Se crea un nuevo string "Hola "
resultado += "Mundo"; // Se crea un nuevo string "Hola Mundo"
Construir un string de tamaño N con concatenaciones repetidas tiene un costo total de O(N) — el tamaño del resultado final.
Los motores de JavaScript modernos optimizan este caso con estructuras internas que evitan copiar el string completo en cada paso, así que en la práctica += en un loop es bastante eficiente. Pero la regla general sigue siendo útil: si vas a construir texto pieza por pieza, piensa en el tamaño del resultado final, no solo en cuántas piezas tienes.
3. ¿Cuál es la diferencia entre strings y arrays?
Ambos son secuencias contiguas con acceso por índice O(1), pero los arrays son mutables y los strings no. Esa diferencia cambia todo: los arrays tienen push y pop, los strings tienen métodos de texto como slice, split y replace.
Primos, no gemelos. Los arrays te permiten modificar su contenido directamente: cambiar un elemento por índice, agregar al final, quitar del inicio. Los strings no. Son inmutables, así que no puedes hacer push o pop sobre un string.
Lo que sí tienen los strings son métodos especializados para operaciones comunes con texto: buscar, acortar, dividir, reemplazar. Implementar estos métodos de strings manualmente sería tedioso (y propenso a errores). Por eso los lenguajes nos dan estos métodos listos para usar.
| Característica | Array (dinámico) | String |
|---|---|---|
| Secuencia contigua | ✅ | ✅ |
| Acceso por índice | ✅ O(1) | ✅ O(1) |
| Mutable | ✅ | ❌ |
| push / pop | ✅ | ❌ |
| Métodos de texto | ❌ | ✅ (search, slice, split, etc.) |
| Redimensionable | ✅ (crece automáticamente) | ❌ (se crea uno nuevo) |
4. Operaciones comunes con strings y su complejidad
Vamos a las operaciones que más vas a usar con strings. Cada una con un ejemplo en JavaScript y su complejidad.
Obtener longitud
Igual que con los arrays, podemos saber cuántos caracteres tiene un string con .length.
const texto = "Hola Mundo";
console.log(texto.length); // 10
Complejidad: O(1) — el acceso es inmediato
Acceso por índice
Ya lo vimos, pero vale la pena repetirlo: puedes acceder a cualquier carácter directamente por su posición.
const texto = "JavaScript";
console.log(texto[0]); // "J"
console.log(texto[4]); // "S"
Complejidad: O(1) — acceso directo, como el elevador del artículo de arrays.
Buscar dentro de un string
Podemos verificar si un string contiene cierto texto con includes(), o encontrar la posición exacta con indexOf().
const frase = "Los arrays son geniales";
console.log(frase.includes("arrays")); // true
console.log(frase.includes("strings")); // false
console.log(frase.indexOf("arrays")); // 4
console.log(frase.indexOf("strings")); // -1 (no lo encontró)
Complejidad: O(n·m) en el peor caso — donde n es la longitud del string y m la longitud del texto que buscas. En la práctica, los motores modernos usan algoritmos de búsqueda optimizados que son mucho más rápidos en la mayoría de los casos.
Extraer una porción (slice / substring)
slice() te permite extraer una parte del string sin modificar el original (recuerda: inmutabilidad).
const texto = "JavaScript";
console.log(texto.slice(0, 4)); // "Java"
console.log(texto.slice(4)); // "Script"
console.log(texto); // string original
Complejidad: O(n) en el peor caso — donde n es el tamaño de la porción extraída. Algunos motores optimizan esta operación para evitar copiar cuando es posible.
Dividir un string (split)
split() divide un string en un array de substrings usando un separador.
const csv = "nombre,edad,ciudad";
const campos = csv.split(",");
console.log(campos); // ["nombre", "edad", "ciudad"]
Complejidad: O(n) — recorre todo el string para encontrar los separadores y crear los substrings.
Concatenar strings
Ya hablamos de esto, pero aquí va el resumen con código:
const nombre = "Hola";
const saludo = nombre + " Mundo";
console.log(saludo); // "Hola Mundo"
Complejidad: O(n) — donde n es el tamaño del resultado final. Recuerda que los motores modernos optimizan esta operación por debajo.
Si quieres explorar todos los métodos de strings disponibles en JavaScript, la referencia de String en MDN es un excelente recurso.
5. ¿Cuál es la complejidad Big O de las operaciones con strings?
Aquí te dejo la tabla de complejidad, en el mismo formato que usamos en el artículo de arrays para que puedas compararlas:
| Operación | Complejidad |
|---|---|
| Acceso por índice | O(1) |
| Obtener longitud | O(1) |
| Buscar un substring | O(n·m) |
| Concatenar (resultado total) | O(n) |
| Slice / Substring | O(n) |
| Split | O(n) |
Nota: las complejidades de búsqueda y concatenación representan el peor caso. Los motores modernos de JavaScript implementan optimizaciones que hacen que en la práctica sean más rápidas de lo que sugiere la teoría.
Compara esta tabla con la del artículo de arrays. Vas a notar que el acceso por índice es igual de rápido en ambos. La diferencia está en que los strings no tienen operaciones como push o pop porque son inmutables. Cada vez que necesitas un string "modificado", estás pagando el costo de crear uno nuevo.
Algo importante: en esta tabla y a lo largo del artículo usamos Big O para describir el costo de las operaciones. Big O es una notación que nos dice cómo crece el tiempo de una operación conforme crece el tamaño de la entrada. O(1) significa tiempo constante (siempre tarda lo mismo sin importar el tamaño), y O(n) significa tiempo lineal (entre más grande la entrada, más tarda, proporcionalmente).
No es el foco de este artículo, pero es importante que tengas el contexto. Si quieres que hagamos un artículo dedicado a Big O, dime en los comentarios.
Conclusión
Los strings son como los primos de los arrays: comparten la misma base (secuencia contigua, acceso por índice) pero juegan con reglas diferentes gracias a la inmutabilidad. Eso los hace predecibles y seguros, pero también significa que operaciones que parecen simples, como concatenar en un loop, pueden ser más costosas de lo que esperabas.
Si leíste con atención, quizá notaste que dije varias veces "en el peor caso". Eso es porque los motores de JavaScript modernos hacen optimizaciones que a veces rompen el modelo mental simple que vimos aquí.
En el siguiente artículo de la serie vamos a poner en práctica todo lo que hemos visto de arrays y strings con problemas y patrones comunes de entrevistas técnicas. Ahí es donde todo se conecta. 💪
Si quieres avanzar con el siguiente tema intenta resolver esto: dado un string, ¿cómo lo invertirías sin usar .reverse()? Piensa en lo que vimos de inmutabilidad. En el siguiente artículo vamos a resolver este tipo de problemas.
Gracias por llegar hasta el final. Estaré pendiente de tus comentarios 🙌🏻
Comentarios