TypeScript Preguntas de entrevista
Las preguntas de entrevista de TypeScript evalúan tu comprensión de tipado estático, inferencia de tipos y características avanzadas del sistema de tipos. Se preguntan comúnmente para roles de frontend, backend y full-stack para asegurar que puedas escribir código escalable y mantenible. Espera una mezcla de explicaciones conceptuales y problemas prácticos de codificación.
Lo que cubren las entrevistas de TypeScript
Tipos e Interfaces
Comprende la diferencia entre alias de tipo e interfaces, y cuándo usar cada uno. Cubre tipos de objetos, firmas de funciones y fusión de declaraciones.
Genéricos
Sabe cómo crear componentes y funciones reutilizables y type-safe usando parámetros genéricos. Incluye restricciones y tipos por defecto.
Inferencia y Estrechamiento de Tipos
Demuestra cómo TypeScript infiere tipos y los estrecha usando condicionales, uniones discriminadas y type guards.
Tipos Utilitarios y Condicionales
Aprovecha tipos utilitarios incorporados (por ejemplo, Partial, Pick) y crea tipos condicionales personalizados para transformar tipos dinámicamente.
Ejemplos de preguntas de entrevista sobre TypeScript
- Explica la diferencia entre `type` e `interface` en TypeScript. ¿Cuándo usarías uno sobre el otro?Lo que cubre una buena respuesta
- `type` puede representar uniones, intersecciones, tipos primitivos y objetos; `interface` solo objetos y funciones.
- `interface` soporta declaración merging (fusionar declaraciones del mismo nombre); `type` no.
- `interface` puede ser extendida (extends) e implementada (implements) por clases; `type` usa intersecciones (&).
- `type` permite propiedades computadas (computed properties) usando keyof y mapeo; `interface` no.
- Se recomienda usar `interface` para APIs públicas o cuando se espera extensión, y `type` para tipos complejos o uniones.
Ver respuesta de ejemplo
La principal diferencia entre `type` e `interface` en TypeScript es que `type` puede representar cualquier tipo, incluyendo uniones, intersecciones y tipos primitivos, mientras que `interface` se limita a objetos y funciones. Además, `interface` es extensible mediante declaration merging, lo que permite añadir propiedades en múltiples declaraciones, algo que `type` no soporta. Por otro lado, `type` permite crear tipos más flexibles mediante intersecciones (&) y puede usar propiedades computadas con keyof. La elección entre uno y otro depende del contexto: si se espera que el tipo sea extendido o implementado por clases, es preferible `interface`. Para tipos complejos como uniones (por ejemplo, `type Status = 'activo' | 'inactivo'`) o intersecciones, `type` es más adecuado. Un pitfall común es usar `type` cuando se necesita declaration merging, lo que obligaría a refactorizar a `interface`. En resumen, `interface` es mejor para APIs públicas y contratos de objetos, mientras que `type` es más versátil para tipos dinámicos y complejos.
- Implementa un tipo genérico `DeepReadonly<T>` que haga todas las propiedades y propiedades anidadas de solo lectura.Lo que cubre una buena respuesta
- Utiliza un tipo mapeado recursivo para hacer readonly en todas las propiedades.
- Maneja correctamente funciones y arrays para evitar errores de recursión.
- Considera que `Readonly` no afecta objetos anidados, por lo que se necesita recursión.
- El tipo debe excluir tipos primitivos y evitar recursión infinita en objetos circulares.
Ver respuesta de ejemplo
El tipo `DeepReadonly<T>` convierte todas las propiedades de un objeto, incluyendo objetos anidados, en readonly. Se implementa usando un tipo mapeado condicional que verifica si la propiedad es un objeto (pero no una función, array o tipo primitivo) y aplica `DeepReadonly` recursivamente. Para evitar errores con funciones, arrays y otros tipos complejos, se deben excluir explícitamente usando condiciones como `T extends Function` o `T extends any[]`. Un desafío común es manejar objetos circulares que causan recursión infinita, pero en la práctica TypeScript tiene límites de profundidad. La complejidad temporal es O(N) donde N es el número de propiedades, y la espacial es similar al tamaño del tipo.
Solución de referenciatypescript type DeepReadonly<T> = { readonly [P in keyof T]: T[P] extends object ? T[P] extends Function ? T[P] : T[P] extends any[] ? ReadonlyArray<DeepReadonly<T[P][number]>> : DeepReadonly<T[P]> : T[P]; }; // Ejemplo: interface User { name: string; address: { city: string; zip: number; }; } type ReadonlyUser = DeepReadonly<User>; // { readonly name: string; readonly address: { readonly city: string; readonly zip: number; } } - Escribe una función genérica `filterArray<T>(arr: T[], predicate: (item: T) => item is T): T[]` que filtre basado en un predicate de tipo.Lo que cubre una buena respuesta
- La función acepta un array y un predicate que es un type guard (retorna `item is T`).
- El type guard permite estrechar el tipo de salida a un subconjunto del array original.
- Se debe usar el type guard en el método filter para que TypeScript infiera el tipo correcto.
- La función retorna `T[]`, pero el predicate puede filtrar subtipos si se usa un tipo más específico.
Ver respuesta de ejemplo
La función `filterArray` es genérica y utiliza un type guard en el predicate para que TypeScript pueda inferir que el array resultante contiene solo elementos que cumplen la condición. El tipo del predicate es `(item: T) => item is T`, pero en realidad puede devolver un subtipo de T si el type guard especifica un tipo más estrecho. Implementar esto correctamente requiere que el predicate sea un type guard explícito. Un error común es usar un predicate que devuelve `boolean` en lugar de `item is T`, lo que hace que el tipo de salida sea `T[]` sin estrechar. La función simplemente delega en `Array.prototype.filter` con el mismo predicate, aprovechando la sobrecarga de tipos de filter que acepta type guards. La complejidad es O(n) y la memoria es O(n) para el nuevo array.
Solución de referenciatypescript function filterArray<T, S extends T>(arr: T[], predicate: (item: T) => item is S): S[] { return arr.filter(predicate); } // Ejemplo: const mixed: (string | number)[] = ['hola', 42, 'mundo', 100]; const strings = filterArray(mixed, (item): item is string => typeof item === 'string'); // strings es string[] - ¿Qué es el tipo `unknown` y en qué se diferencia de `any`? Proporciona un ejemplo de cuándo usar cada uno.Lo que cubre una buena respuesta
- `unknown` es el tipo seguro de `any`: no se puede operar sin antes estrecharlo.
- `any` permite cualquier operación sin verificación de tipo, eliminando el tipado.
- `unknown` obliga a usar type guards (typeof, instanceof, etc.) antes de usar el valor.
- Usa `any` para migraciones rápidas o cuando no se conoce el tipo, pero evita en producción.
- Usa `unknown` cuando se recibe datos externos (API, input de usuario) y se quiere seguridad.
Ver respuesta de ejemplo
`unknown` es la contraparte segura de `any`. Mientras que `any` desactiva la verificación de tipos y permite cualquier operación, `unknown` requiere que se realice un estrechamiento (type narrowing) antes de usar el valor. Por ejemplo, no se puede acceder a propiedades de un `unknown` sin antes verificar su tipo. Esto previene errores en tiempo de ejecución y fomenta el tipado seguro. Por otro lado, `any` es útil en situaciones de migración de JavaScript a TypeScript o cuando el tipo es completamente impredecible, pero su uso excesivo elimina los beneficios del tipado estático. Un ejemplo común de uso de `unknown` es al parsear JSON: `const data: unknown = JSON.parse(jsonString)`; luego se debe verificar la estructura. Para `any`, se podría usar en un polyfill o cuando se trabaja con librerías sin tipos. La recomendación es preferir `unknown` sobre `any` siempre que sea posible.
- ¿Cómo tiparías una función async que devuelve una Promise de un objeto usuario? Muestra cómo manejar los estados resuelto y rechazado.Lo que cubre una buena respuesta
- La función async devuelve una Promise del tipo de retorno, por lo que se tipa como `Promise<User>`.
- Se maneja el estado resuelto con `await` y el rechazado con try/catch o .catch().
- El tipo User debe estar definido con las propiedades esperadas.
- Se puede usar un tipo de retorno que incluya errores para manejar ambos estados de forma tipada.
Ver respuesta de ejemplo
Para tipar una función async que devuelve un usuario, se define el tipo del objeto usuario (interface User) y la función retorna `Promise<User>`. Al usar async/await, el manejo del estado resuelto se hace con `await` y el rechazado con try/catch. También se puede encadenar .then().catch(). Una buena práctica es definir un tipo de retorno que represente ambos estados, por ejemplo usando un tipo unión o una tupla, para que el consumidor maneje explícitamente el error. Sin embargo, lo más común es lanzar excepciones y capturarlas. Ejemplo: `async function getUser(id: string): Promise<User> { ... }`. Luego, al llamarla, se usa try { const user = await getUser('1'); } catch (error) { ... }. Si se quiere tipar el error, se puede usar `unknown` y luego verificar. Un pitfall es olvidar que el error puede ser de cualquier tipo, por lo que se debe evitar `catch(e: any)` y mejor usar `unknown` con type guards.
Solución de referenciatypescript interface User { id: string; name: string; email: string; } async function getUser(id: string): Promise<User> { const response = await fetch(`/api/users/${id}`); if (!response.ok) { throw new Error('Error fetching user'); } return response.json() as Promise<User>; } // Uso: try { const user = await getUser('123'); console.log(user.name); } catch (error) { // error es unknown if (error instanceof Error) { console.error(error.message); } } - Diseña una clase event emitter type-safe que imponga nombres de eventos y tipos de payload.Lo que cubre una buena respuesta
- Usa tipos genéricos para mapear nombres de eventos a sus tipos de payload.
- La clase tiene métodos on, off, emit con restricciones basadas en el mapa de eventos.
- El método on registra un callback que recibe el payload del tipo correspondiente.
- El método emit dispara el evento con el payload, notificando a los listeners.
- La implementación debe ser type-safe para evitar errores en tiempo de compilación.
Ver respuesta de ejemplo
Un event emitter type-safe se diseña usando un tipo genérico que asocia nombres de eventos con sus payloads, por ejemplo `interface EventMap { click: { x: number; y: number }; keypress: { key: string } }`. Luego, la clase `EventEmitter<E extends Record<string, any>>` tiene métodos on, off y emit que utilizan `keyof E` para restringir los nombres y `E[K]` para los tipos de payload. El método on registra un callback del tipo `(payload: E[K]) => void`. La implementación interna almacena los callbacks en un mapa (por ejemplo, `Map<K, Set<Function>>`). Un desafío común es manejar la eliminación de listeners de forma segura y evitar fugas de memoria. Además, la emisión debe iterar sobre todos los callbacks registrados. Este patrón garantiza que solo se puedan emitir eventos definidos y con el payload correcto, mejorando la robustez del código.
Solución de referenciatypescript type EventHandler<T> = (payload: T) => void; class EventEmitter<Events extends Record<string, any>> { private listeners: Map<keyof Events, Set<EventHandler<any>>> = new Map(); on<K extends keyof Events>(event: K, handler: EventHandler<Events[K]>): void { if (!this.listeners.has(event)) { this.listeners.set(event, new Set()); } this.listeners.get(event)!.add(handler); } off<K extends keyof Events>(event: K, handler: EventHandler<Events[K]>): void { const handlers = this.listeners.get(event); if (handlers) { handlers.delete(handler); if (handlers.size === 0) { this.listeners.delete(event); } } } emit<K extends keyof Events>(event: K, payload: Events[K]): void { const handlers = this.listeners.get(event); if (handlers) { handlers.forEach(handler => handler(payload)); } } } // Ejemplo: interface MyEvents { login: { userId: string }; logout: {}; } const emitter = new EventEmitter<MyEvents>(); emitter.on('login', (payload) => console.log(payload.userId)); emitter.emit('login', { userId: '123' }); - Explica cómo funcionan los operadores `keyof` y `typeof`. Proporciona ejemplos de usarlos para crear tipos dinámicos.Lo que cubre una buena respuesta
- `keyof` devuelve la unión de claves de un tipo de objeto.
- `typeof` en un contexto de tipo obtiene el tipo de una expresión (variable, función, etc.).
- Se pueden combinar para crear tipos dinámicos, como `keyof typeof obj`.
- Ejemplo: `type Keys = keyof Person` da 'name' | 'age' si Person tiene esas propiedades.
- Ejemplo: `type TypeOfVar = typeof variable` obtiene el tipo de la variable.
Ver respuesta de ejemplo
`keyof` es un operador de tipo que, dado un tipo de objeto, produce una unión de sus nombres de propiedad (claves). Por ejemplo, si `interface Persona { nombre: string; edad: number }`, entonces `keyof Persona` es `'nombre' | 'edad'`. `typeof`, en un contexto de tipo, se usa para obtener el tipo de una expresión, como una variable o una función. Por ejemplo, `const x = { a: 1 }; type T = typeof x;` da `{ a: number }`. Se pueden combinar para crear tipos dinámicos: `type Keys = keyof typeof obj` obtiene las claves de un objeto específico. También son útiles para crear tipos mapeados, como `type Mapped = { [K in keyof T]: boolean }`. Un uso común es con genéricos para restringir propiedades: `function getProp<T, K extends keyof T>(obj: T, key: K): T[K]`. Un pitfall es que `typeof` en un tipo solo funciona con expresiones que tienen un valor en tiempo de ejecución, no con tipos. Además, `keyof` solo funciona con tipos que tienen índices (objetos, arrays, etc.).
- Usa tipos mapeados para crear un tipo `FlagProperties<T>` que convierta todas las propiedades booleanas de un tipo de objeto `T` a literales de cadena 'true' | 'false'.Lo que cubre una buena respuesta
- Usa un tipo mapeado con keyof T para iterar sobre las propiedades.
- Condición para verificar si el tipo de la propiedad es boolean.
- Si es boolean, se asigna el literal 'true' | 'false', de lo contrario se mantiene el tipo original.
- Se puede usar `T[P] extends boolean` para detectar boolean, pero cuidado con `true` y `false` literales.
Ver respuesta de ejemplo
Para crear un tipo `FlagProperties<T>` que convierta propiedades booleanas en literales de cadena, se utiliza un tipo mapeado condicional. Se itera sobre cada propiedad `P` de `T` y se verifica si `T[P]` es exactamente el tipo `boolean` (incluyendo los literales `true` y `false`). Si lo es, se asigna `'true' | 'false'`; de lo contrario, se mantiene el tipo original `T[P]`. Es importante usar la condición `T[P] extends boolean` porque tanto `boolean` como `true` y `false` son subtipos de `boolean`. Sin embargo, esto también convertirá propiedades con el tipo `true` (literal) en el literal union, lo cual puede ser deseado. Un detalle es que si se quiere solo para `boolean` estricto (no literales), se debe usar una condición más específica con `[]` para evitar distribución. La implementación es directa y útil para tipar respuestas de APIs donde los flags booleanos se serializan como cadenas.
Solución de referenciatypescript type FlagProperties<T> = { [P in keyof T]: T[P] extends boolean ? 'true' | 'false' : T[P]; }; // Ejemplo: interface Config { debug: boolean; port: number; verbose: boolean; } type Flagged = FlagProperties<Config>; // { debug: 'true' | 'false'; port: number; verbose: 'true' | 'false' }
Cómo prepararse
- Practica implementando tipos utilitarios desde cero (por ejemplo, Partial, Pick, ReturnType).
- Comprende el tipado estructural (duck typing) y cómo afecta la compatibilidad de tipos.
- Domina patrones avanzados como uniones discriminadas y tipos marcados.
- Usa el playground de TypeScript y desafíos oficiales (por ejemplo, type challenges) para refinar tus habilidades.
- Revisa codebases reales de TypeScript (por ejemplo, librerías de React o Node.js) para ver patrones en acción.
Preguntas frecuentes
¿Necesito saber TypeScript para un trabajo con React?
Sí, porque la mayoría de los codebases modernos de React usan TypeScript para detectar errores temprano y mejorar la experiencia del desarrollador.
¿Cómo puedo practicar TypeScript para entrevistas?
Usa el playground de TypeScript, resuelve desafíos de tipos en GitHub (por ejemplo, type-challenges) e implementa pequeños proyectos.
¿Cuáles son los errores comunes en entrevistas de TypeScript?
Abusar de `any`, ignorar el modo estricto y no entender cómo funcionan los tipos condicionales.
¿Es difícil aprender TypeScript?
Lo básico es fácil, pero las características avanzadas como tipos condicionales y tipos de literales de plantilla requieren práctica.
¿Debería memorizar definiciones de tipos?
Enfócate en entender conceptos en lugar de memorizar. Saber cuándo usar genéricos o tipos utilitarios es más importante.
Practica preguntas sobre TypeScript con retroalimentación instantánea de IA
Sube tu currículum, obtén una entrevista simulada personalizada y ve exactamente qué mejorar — gratis para empezar.