Guías

15 preguntas de entrevista sobre regex, con soluciones explicadas

Quince ejercicios de regex como los que se hacen en entrevistas, desde validar un nombre de usuario hasta explicar por qué un patrón congela un servidor, cada uno con su solución, los casos límite que te van a plantear y notas sobre rendimiento.

Logo de Regex Hunter

· 14 min de lectura

/^(?!.*__)[a-z][a-z0-9_]{1,14}[a-z0-9]$/

atrapa

  • night_owl
  • dev42
  • x_ray

deja libre

  • _admin
  • 9lives
  • owl_

Las buenas preguntas de entrevista sobre regex comprueban si sabes escribir un patrón correcto, legible y seguro ante entradas no válidas: validar un nombre de usuario, cambiar el formato de fechas, extraer campos de una línea de log, enmascarar un número de tarjeta y explicar por qué un patrón congela un servidor. Abajo tienes 15 ejercicios así con soluciones explicadas, los casos límite en los que insistirá el entrevistador y notas sobre rendimiento donde importa.

Todos los patrones son de JavaScript y todos los ejemplos son interactivos: las coincidencias se resaltan mientras lees. Cuando Python o Java se comportan de otra forma, la solución lo indica.

01Validar un nombre de usuario

La tarea: de 3 a 16 caracteres; letras minúsculas, dígitos y guiones bajos; empieza por una letra, no termina en guion bajo y nunca tiene dos guiones bajos seguidos.

Coincidencias9/9 como se esperaba
/^(?!.*__)[a-z][a-z0-9_]{1,14}[a-z0-9]$/

Debe coincidir3/3

  • night_owl
  • dev42
  • x_ray

No debe6/6

  • _admin
  • 9lives
  • night__owl
  • owl_
  • ab
  • Night_owl
Pruébalo en la Zona de pruebas

Por qué funciona: el primer y el último carácter tienen sus propias clases, y el centro admite de 1 a 14, así que el total es de 3 a 16. El lookahead (?!.*__) revisa toda la cadena una vez en busca de un guion bajo doble antes de consumir nada.

Casos límite: sin ^ y $, !!night_owl!! pasa. Evita \w aquí: admite mayúsculas y, en Python 3, coincide con letras de cualquier sistema de escritura.

02Cambiar fechas de DD/MM/AAAA a AAAA-MM-DD

La tarea: reescribir todas las fechas de un bloque de texto sin tocar nada más.

Coincidencias5/5 como se esperaba
/\b(\d{2})\/(\d{2})\/(\d{4})\b/

Debe coincidir2/2

  • Due 04/11/2026
  • From 05/01/2027 to 12/01/2027

No debe3/3

  • Already ISO: 2026-11-04
  • 4/11/2026
  • Order 104/11/20261
Pruébalo en la Zona de pruebas

El reemplazo vuelve a colocar los grupos en otro orden:

text.replace(/\b(\d{2})\/(\d{2})\/(\d{4})\b/g, '$3-$2-$1')
// "Due 04/11/2026" → "Due 2026-11-04"

Con grupos con nombre, el reemplazo se entiende solo: (?<d>\d{2})\/(?<m>\d{2})\/(?<y>\d{4}) y '$<y>-$<m>-$<d>'. En Python sería \g<y>-\g<m>-\g<d> con grupos (?P<y>…).

Casos límite: el \b a cada lado impide coincidir dentro de un número más largo. Una regex comprueba la forma, no el calendario: 31/02/2026 pasa, así que después comprueba las fechas reales con una librería de fechas. Más sobre esto en Regex para fechas.

03Extraer campos de una línea de log de acceso

La tarea: de una línea de log de un servidor web, obtener la IP, la hora, el método, la ruta, el estado y el tamaño.

Coincidencias3/3 como se esperaba
/^(?<ip>\S+) \S+ \S+ \[(?<time>[^\]]+)\] "(?<method>[A-Z]+) (?<path>\S+) [^"]*" (?<status>\d{3}) (?<bytes>\d+|-)$/

Debe coincidir2/2

  • 203.0.113.9 - - [04/Nov/2026:10:15:32 +0000] "GET /blog/regex-games HTTP/1.1" 200 5123
  • 198.51.100.4 - - [04/Nov/2026:10:16:01 +0000] "POST /api/login HTTP/2.0" 401 -

No debe1/1

  • 203.0.113.9 - - [04/Nov/2026:10:15:32 +0000] "GET / HTTP/1.1"
Pruébalo en la Zona de pruebas

Por qué funciona: cada campo se reconoce por lo que no puede contener: \S+ para un campo sin espacios, [^\]]+ hasta el corchete de cierre, [^"]* hasta la comilla de cierre. Los grupos con nombre hacen que el resultado se explique solo: line.match(re).groups.status es "200".

Rendimiento: las clases negadas nunca se pasan de largo, así que una línea mala falla rápido. La versión tentadora con .* entre campos puede probar muchas formas de dividir una línea larga antes de rendirse.

Casos límite: una petición mal formada se registra como "-", que este patrón rechaza; decide si saltar esas líneas o añadir |- para ellas.

04Quedarse solo con las líneas que no mencionan DEBUG

La tarea: filtrar un log para dejar solo las líneas sin la palabra DEBUG.

Coincidencias4/4 como se esperaba
/^(?!.*\bDEBUG\b).*$/m

Debe coincidir2/2

  • INFO server started
  • DEBUGGER attached

No debe2/2

  • DEBUG cache miss
  • warn: DEBUG off
Pruébalo en la Zona de pruebas

Por qué funciona: el lookahead negativo busca primero la palabra en la línea; solo si no está, .* toma la línea. El \b a cada lado hace que DEBUGGER se quede.

Qué decir después: en código real, lines.filter(l => !/\bDEBUG\b/.test(l)) o grep -v -w DEBUG es más claro. Saber ambas cosas es justo lo que evalúa la pregunta.

05Obtener el texto entre corchetes

La tarea: de [error] disk full [code 28], obtener error y code 28.

Coincidencias4/4 como se esperaba
/\[[^\]]*\]/

Debe coincidir2/2

  • [error] disk full [code 28]
  • []

No debe2/2

  • no brackets here
  • [unclosed
Pruébalo en la Zona de pruebas

La trampa: \[(.*)\] es codicioso. Llega hasta el último ] y devuelve error] disk full [code 28. El perezoso \[(.*?)\] funciona, pero [^\]]* dice exactamente lo que quieres y nunca retrocede. Hay más en Cuantificadores codiciosos y perezosos.

06Encontrar comentarios TODO que nombran a un responsable

La tarea: listar los comentarios // TODO(name): note, con el responsable y la nota.

Coincidencias5/5 como se esperaba
///\s*TODO\((\w+)\):\s*(.+)/

Debe coincidir2/2

  • // TODO(ola): retry on timeout
  • x = 1 //TODO(dev_ops): remove

No debe3/3

  • // TODO: no owner
  • // todo(ola): lowercase
  • # TODO(ola): python comment
Pruébalo en la Zona de pruebas

Casos límite: añade el modificador i si tu equipo escribe todo. Para cubrir también comentarios con #, empieza con (?://|#). Y dilo en voz alta: una regex no distingue un comentario de una cadena que contiene //, así que una regla de linter es la respuesta robusta.

07Enmascarar un número de tarjeta salvo los cuatro últimos dígitos

La tarea: convertir 4111111111111111 en ************1111.

Coincidencias4/4 como se esperaba
/\d(?=\d{4})/

Debe coincidir2/2

  • 4111111111111111
  • card 5500000000000004

No debe2/2

  • 1234
  • no digits
Pruébalo en la Zona de pruebas
'4111111111111111'.replace(/\d(?=\d{4})/g, '*')   // "************1111"

Por qué funciona: el lookahead (?=\d{4}) coincide con un dígito solo cuando lo siguen cuatro dígitos más, sin consumirlos, así que los cuatro últimos nunca se reemplazan.

Casos límite: con espacios, 4111 1111 1111 1111, no se enmascara nada, porque ningún dígito va seguido directamente de cuatro dígitos. \d(?=(?:\D*\d){4}) permite separadores entre medias y da **** **** **** 1111.

08Convertir camelCase a snake_case

La tarea: getUserId → get_user_id, y parseHTTPResponse → parse_http_response.

Patrón47 car.
/(?<=[a-z0-9])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])/
Pruébalo en la Zona de pruebas
Coincide con la posición vacía entre dos palabras, así que el reemplazo inserta “_” sin quitar nada.
s.replace(/(?<=[a-z0-9])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])/g, '_').toLowerCase()

Por qué funciona: el patrón no coincide con ningún carácter, solo con posiciones: después de una minúscula o un dígito y antes de una mayúscula, o dentro de una serie de mayúsculas justo antes de la última (la P de HTTPResponse). Solo con la primera mitad se obtiene parse_httpresponse, una respuesta incorrecta habitual.

Casos límite: el lookbehind funciona en todos los navegadores actuales, Node y Python (el módulo re de Python lo exige de ancho fijo, como aquí). Sin lookbehind, captura la letra: ([a-z0-9])([A-Z]) → '$1_$2'.

09Validar una duración como 1h30m

La tarea: aceptar 1h30m, 45m y 2h, pero no una cadena vacía.

Coincidencias7/7 como se esperaba
/^(?=\d)(?:(\d+)h)?(?:(\d+)m)?$/

Debe coincidir3/3

  • 1h30m
  • 45m
  • 2h

No debe4/4

  • h
  • 30m1h
  • 1h 30m
  • 90
Pruébalo en la Zona de pruebas

Por qué funciona: las dos partes son opcionales, así que sin el lookahead el patrón también acepta la cadena vacía. (?=\d) dice “aquí tiene que haber algo, y empieza por un dígito”. Es un truco habitual siempre que todas las partes de un patrón son opcionales.

Casos límite: 1h90m pasa. Si los minutos deben ser menos de 60, usa ([0-5]?\d)m, o comprueba el número en el código, que se lee mejor.

10Encontrar encabezados de Markdown, niveles 1 a 3

La tarea: en un archivo Markdown, encontrar los encabezados #, ## y ### y capturar su texto.

Coincidencias5/5 como se esperaba
/^(#{1,3})\s+(\S.*)$/m

Debe coincidir2/2

  • # Title
  • ### Install

No debe3/3

  • #### Too deep
  • #hashtag
  • Not a # heading
Pruébalo en la Zona de pruebas

Por qué funciona: #{1,3} no puede estirarse a cuatro, porque \s+ debe ir justo después. El modificador m hace que ^ y $ funcionen por línea en todo un documento (consulta Anclas en regex).

Casos límite: una línea con # dentro de un bloque de código no es un encabezado, y una regex no puede ver los bloques de código. Ese es el momento de mencionar un parser de Markdown.

11Dividir una línea CSV con campos entre comillas

La tarea: dividir widget,"Sizes 2, 4 and 6",9.99 en tres campos, conservando la coma dentro de las comillas.

Patrón29 car.
/(?:^|,)("(?:[^"]|"")*"|[^,]*)/
Pruébalo en la Zona de pruebas
Cada coincidencia es un campo: un campo entre comillas (donde "" es una comilla escapada) o texto normal hasta la siguiente coma.

Con el modificador g, el grupo 1 de cada coincidencia es un campo: widget, "Sizes 2, 4 and 6" y 9.99. Después quita las comillas exteriores y convierte "" en ".

Qué decir después: los campos entre comillas pueden contener saltos de línea, que una regex línea a línea no puede manejar. Para CSV real, usa un parser (el módulo csv de Python, Papa Parse en JavaScript). Una buena respuesta de entrevista da la regex y dice eso.

12Encontrar caracteres fuera de ASCII

La tarea: marcar el texto que contiene letras acentuadas, símbolos como € o emojis, por ejemplo antes de escribir en un sistema que solo acepta ASCII.

Coincidencias5/5 como se esperaba
/[^\x00-\x7F]/

Debe coincidir3/3

  • café
  • naïve résumé
  • price: 5 €

No debe2/2

  • plain ASCII text
  • 100% ok!
Pruébalo en la Zona de pruebas

Casos límite: sin el modificador u, un emoji son dos unidades UTF-16 y ambas coinciden; con u es un solo carácter. Para permitir solo ASCII imprimible, dale la vuelta: ^[\x20-\x7E]*$.

13Validar una etiqueta de un nombre de host

La tarea: una etiqueta (la parte entre puntos) tiene de 1 a 63 letras, dígitos o guiones, y no puede empezar ni terminar con guion.

Coincidencias7/7 como se esperaba
/^(?!-)[a-z0-9-]{1,63}(?<!-)$/i

Debe coincidir3/3

  • regexhunter
  • my-site-2
  • a

No debe4/4

  • -start
  • end-
  • under_score
  • aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
Pruébalo en la Zona de pruebas

Por qué funciona: el lookahead y el lookbehind protegen los dos extremos, así que el límite de longitud queda en un solo sitio, {1,63}. La última línea tiene 64 caracteres, uno de más.

Casos límite: sin lookarounds queda ^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$, que es correcto pero más difícil de comprobar. Un nombre de host completo son etiquetas unidas por puntos, con 253 caracteres como máximo, y ese límite es más fácil de comprobar en código.

14Explicar por qué un patrón congela el servidor

La tarea: esta comprobación de “palabras separadas por un solo espacio” funciona en los tests, pero algunas entradas congelan el servidor. ¿Por qué, y cómo lo arreglas?

^(\w+\s?)+$

La respuesta: el espacio es opcional, así que una serie de letras como aaaaaaaa puede repartirse entre las repeticiones del grupo de muchísimas formas (a|aaaaaaa, aa|aaaaaa, a|a|aaaaaa…). Cuando la cadena termina en algo que no puede coincidir, como !, el motor prueba todas las divisiones antes de rendirse. El trabajo se duplica con cada letra extra: en Node, 24 letras y un ! tardaron 8 segundos. Esto es el backtracking catastrófico.

La solución: haz que cada carácter solo pueda coincidir de una forma. Haz obligatorio el separador y permite un espacio opcional al final:

Coincidencias4/4 como se esperaba
/^\w+(?:\s\w+)*\s?$/

Debe coincidir2/2

  • hello world
  • one two three

No debe2/2

  • hello world
  • aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa!
Pruébalo en la Zona de pruebas

El patrón corregido rechaza 30.000 letras y un ! en aproximadamente un milisegundo. Otras buenas respuestas: un grupo atómico o un cuantificador posesivo si el motor los tiene (Java, PCRE, Python 3.11+), o un motor de tiempo lineal como RE2 para entradas no confiables. La historia completa está en Backtracking catastrófico.

15Quitar etiquetas HTML, y saber cuándo parar

La tarea: convertir <p>Hello <b>world</b></p> en Hello world.

Coincidencias3/3 como se esperaba
/<[^>]+>/
  • <p>Hello <b>world</b></p>
  • <br/>
  • 3 < 5 and 7 > 2
  • plain text
Pruébalo en la Zona de pruebas

text.replace(/<[^>]+>/g, '') funciona con marcado simple. La tercera línea muestra dónde falla: un texto plano con < y > pierde < 5 and 7 >. Un atributo como title="x > y" también lo rompe.

La respuesta que esperan: el HTML se anida, y el anidamiento es justo lo que las expresiones regulares no pueden contar. Para sacar el texto de HTML, usa el parser del propio navegador (new DOMParser() y textContent), y para hacer seguro el HTML, usa una librería de sanitización. Una regex sirve para echar un vistazo rápido a marcado de confianza, nunca para seguridad.

16Practica para la entrevista

Leer soluciones ayuda; escribir patrones con un poco de presión ayuda más.

  • Los packs de práctica entrenan una técnica por pack, y la mayoría de los niveles son patrones que escribes en el trabajo: formatos, líneas de log e identificadores en código.
  • La caza del día lleva cinco minutos y mantiene tu regex en forma entre entrevistas.
  • Si la entrevista es en Python, lee Regex en JavaScript y en Python para conocer las diferencias que suelen confundir.