InterviewHack.ai
Start free
Blog/Preguntas de entrevista de Python — 50 con código y respuestas

Preguntas de entrevista de Python — 50 con código y respuestas

16 de septiembre de 2026

pythonbackend

Artículo completo "Preguntas de entrevista de Python — 50 con código y respuestas" insertado en blog_posts (slug: preguntas-entrevista-python, 33.9 KB de body_md, 6 FAQ). Disponible en https://www.interviewhack.ai/blog/preguntas-entrevista-python

Por qué Python sigue siendo el lenguaje favorito de los recruiters técnicos

Python aparece en más del 60 % de las ofertas de trabajo en data, backend y ML. Los entrevistadores lo usan porque permite evaluar algoritmia, diseño de software y productividad en el mismo ejercicio.

Esta guía cubre las 50 preguntas que aparecen con más frecuencia en entrevistas de empresas tech de LATAM, España y empresas globales en remoto. Cada pregunta incluye la respuesta que esperan, el código que tenés que poder escribir de memoria y los errores comunes que te hacen perder puntos.


Bloque 1: Fundamentos del lenguaje

1. ¿Qué es Python y cuáles son sus características principales?

Python es un lenguaje interpretado, dinámicamente tipado y multiparadigma (soporta programación imperativa, orientada a objetos y funcional). Sus pilares son:

  • Sintaxis limpia que prioriza la legibilidad (PEP 8)
  • Gestión automática de memoria con garbage collector
  • Tipado dinámico fuerte (no implícito — "3" + 3 lanza TypeError)
  • Ecosistema gigante: pip, PyPI, y miles de librerías
python
# Tipado dinámico: la variable puede cambiar de tipo
x = 42
x = "ahora soy string"
x = [1, 2, 3]

2. ¿Cuál es la diferencia entre una lista, una tupla y un set?

Esta es la pregunta de calentamiento más común. Memorizá la tabla:

  • list — mutable, ordenada, permite duplicados, acceso O(1) por índice
  • tuple — inmutable, ordenada, permite duplicados, acceso O(1) por índice
  • set — mutable, sin orden, sin duplicados, lookup O(1) promedio
python
lista = [1, 2, 2, 3]      # permite duplicados
tupla = (1, 2, 2, 3)      # inmutable
conjunto = {1, 2, 2, 3}   # => {1, 2, 3}  — elimina duplicados

# Cuándo usar cada uno:
# list   → cuando necesitás orden y mutabilidad
# tuple  → claves de diccionario, retorno múltiple de funciones
# set    → eliminar duplicados, operaciones de intersección/unión rápidas

3. ¿Qué es un diccionario y cómo funciona internamente?

Un dict es una tabla hash. Las claves se hashean para determinar el bucket donde se almacena el valor. Desde Python 3.7 los dicts mantienen el orden de inserción.

python
agenda = {"nombre": "Ana", "edad": 30}

# Acceso seguro — nunca lances KeyError en producción
nombre = agenda.get("nombre", "desconocido")

# Dict comprehension
cuadrados = {n: n**2 for n in range(1, 6)}
# => {1: 1, 2: 4, 3: 9, 4: 16, 5: 25}

# Merge en Python 3.9+
merged = {"a": 1} | {"b": 2}  # => {"a": 1, "b": 2}

Trampa de entrevista: solo los tipos hashables (inmutables) pueden ser claves: strings, números, tuplas. Las listas NO pueden ser clave.

4. ¿Qué significa que Python sea "pass by object reference"?

Python no es pass-by-value ni pass-by-reference. Es pass-by-object-reference: la función recibe una referencia al objeto, no una copia.

python
def agregar(lista, elemento):
    lista.append(elemento)      # modifica el objeto original
    lista = ["nueva lista"]     # solo reasigna la variable local

mi_lista = [1, 2, 3]
agregar(mi_lista, 4)
print(mi_lista)  # => [1, 2, 3, 4]

Regla: si el objeto es mutable (list, dict, set) y lo modificás sin reasignar, el cambio se ve afuera.

5. ¿Cuál es la diferencia entre `==` e `is`?

== compara valor. is compara identidad (mismo objeto en memoria).

python
a = [1, 2, 3]
b = [1, 2, 3]
c = a

print(a == b)   # True  — mismo valor
print(a is b)   # False — objetos distintos en memoria
print(a is c)   # True  — c apunta al mismo objeto que a

# Integer caching (CPython) — gotcha de entrevista
x = 256; y = 256
print(x is y)   # True  — CPython cachea -5 a 256

x = 257; y = 257
print(x is y)   # False — fuera del rango cacheado

Nunca uses is para comparar valores. Solo usalo para None: if x is None.


Bloque 2: Funciones y closures

6. ¿Qué es un argumento `*args` y `**kwargs`?

python
def log(*args, **kwargs):
    # *args   — tupla con todos los posicionales
    # **kwargs — dict con todos los con nombre
    print("args:", args)
    print("kwargs:", kwargs)

log(1, 2, 3, usuario="Ana", nivel="INFO")
# args: (1, 2, 3)
# kwargs: {"usuario": "Ana", "nivel": "INFO"}

7. ¿Qué es una función lambda y cuándo usarla?

Una lambda es una función anónima de una sola expresión.

python
# Uso real: ordenar por criterio personalizado
personas = [{"nombre": "Carlos", "edad": 25}, {"nombre": "Ana", "edad": 30}]
personas.sort(key=lambda p: p["edad"])
ordenadas = sorted(personas, key=lambda p: p["nombre"])

Cuándo NO usar lambda: si necesitás más de una línea o querés reusar la función — usá def con nombre.

8. ¿Qué es un closure?

Un closure es una función que recuerda las variables del scope donde fue creada, aunque ese scope ya no exista.

python
def hacer_multiplicador(factor):
    def multiplicar(numero):
        return numero * factor   # "factor" está en el closure
    return multiplicar

por_tres = hacer_multiplicador(3)
por_cinco = hacer_multiplicador(5)

print(por_tres(10))   # => 30
print(por_cinco(10))  # => 50

9. ¿Qué es un decorador y cómo se implementa?

Un decorador envuelve a otra función para modificar su comportamiento sin cambiarle el código.

python
import time, functools

def medir_tiempo(func):
    @functools.wraps(func)   # preserva __name__, __doc__, etc.
    def wrapper(*args, **kwargs):
        inicio = time.perf_counter()
        resultado = func(*args, **kwargs)
        fin = time.perf_counter()
        print(f"{func.__name__} tardó {fin - inicio:.4f}s")
        return resultado
    return wrapper

@medir_tiempo
def procesar(n):
    return sum(range(n))

procesar(1_000_000)  # => procesar tardó 0.0312s

# Decorador con argumentos
def reintentar(veces=3):
    def decorador(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            for intento in range(veces):
                try:
                    return func(*args, **kwargs)
                except Exception as e:
                    if intento == veces - 1:
                        raise
                    print(f"Intento {intento+1} fallido: {e}")
        return wrapper
    return decorador

10. ¿Cuál es la diferencia entre `@staticmethod` y `@classmethod`?

python
class Circulo:
    PI = 3.14159

    def __init__(self, radio):
        self.radio = radio

    def area(self):                      # método de instancia — recibe self
        return self.PI * self.radio ** 2

    @classmethod
    def desde_diametro(cls, diametro):   # recibe cls — factory method
        return cls(diametro / 2)

    @staticmethod
    def es_valido(radio):                # no recibe nada especial
        return radio > 0

Regla: @classmethod para factories y constructores alternativos. @staticmethod para utilidades que no necesitan estado de instancia ni de clase.


Bloque 3: OOP

11. ¿Cómo funciona la herencia en Python?

python
class Animal:
    def __init__(self, nombre):
        self.nombre = nombre

    def hablar(self):
        raise NotImplementedError

class Perro(Animal):
    def hablar(self):
        return f"{self.nombre} dice: Guau"

class Gato(Animal):
    def hablar(self):
        return f"{self.nombre} dice: Miau"

# Polimorfismo
for a in [Perro("Rex"), Gato("Luna")]:
    print(a.hablar())

12. ¿Qué es el MRO (Method Resolution Order)?

El MRO determina el orden de búsqueda de métodos en herencia múltiple. Usa el algoritmo C3 Linearization.

python
class A:
    def hola(self): return "A"

class B(A):
    def hola(self): return "B"

class C(A):
    def hola(self): return "C"

class D(B, C):
    pass

# MRO: D -> B -> C -> A -> object
print(D().hola())  # => "B"

13. ¿Qué son los dunder methods (métodos mágicos)?

python
class Vector:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __repr__(self):
        return f"Vector({self.x}, {self.y})"

    def __add__(self, otro):
        return Vector(self.x + otro.x, self.y + otro.y)

    def __eq__(self, otro):
        return self.x == otro.x and self.y == otro.y

    def __hash__(self):
        return hash((self.x, self.y))

v1 = Vector(1, 2)
v2 = Vector(3, 4)
print(v1 + v2)   # => Vector(4, 6)

14. ¿Qué es una dataclass?

python
from dataclasses import dataclass, field
from typing import List

@dataclass
class Producto:
    nombre: str
    precio: float
    tags: List[str] = field(default_factory=list)
    activo: bool = True

p = Producto("Laptop", 999.99, ["tech"])
print(p)  # Producto(nombre='Laptop', precio=999.99, tags=['tech'], activo=True)

@dataclass(frozen=True)   # inmutable y hashable
class Punto:
    x: float
    y: float

15. ¿Cuál es la diferencia entre `__str__` y `__repr__`?

  • __repr__: representación inequívoca para desarrolladores. Ideal: eval(repr(obj)) == obj.
  • __str__: representación amigable para usuarios finales.
python
from datetime import datetime
d = datetime(2024, 1, 15)
print(repr(d))   # datetime.datetime(2024, 1, 15, 0, 0)
print(str(d))    # 2024-01-15 00:00:00
print(f"{d!r}")  # usa repr en f-strings

Bloque 4: Iteradores, generadores y comprehensions

16. ¿Qué es un iterador y cómo se implementa?

python
class ContadorHasta:
    def __init__(self, maximo):
        self.maximo = maximo
        self.actual = 0

    def __iter__(self):
        return self

    def __next__(self):
        if self.actual >= self.maximo:
            raise StopIteration
        self.actual += 1
        return self.actual

for n in ContadorHasta(5):
    print(n)   # 1 2 3 4 5

17. ¿Qué es un generador y por qué es más eficiente?

Un generador usa yield. No carga todos los valores en memoria — los produce uno por uno (lazy).

python
def numeros_gen(n):
    for i in range(n):
        yield i ** 2

# Para n=1_000_000:
# lista  — ~8 MB | generador — ~200 bytes

gen = numeros_gen(5)
print(next(gen))   # 0
print(next(gen))   # 1

# Generator expression — como list comprehension pero lazy
cuadrados = (x**2 for x in range(1_000_000))

18. ¿Qué hace `yield from`?

python
def aplanar(lista):
    for elemento in lista:
        if isinstance(elemento, list):
            yield from aplanar(elemento)
        else:
            yield elemento

resultado = list(aplanar([1, [2, [3, 4]], 5]))
# => [1, 2, 3, 4, 5]

19. ¿Cuál es la diferencia entre `map()`, `filter()` y list comprehensions?

python
numeros = [1, 2, 3, 4, 5, 6]

dobles_map  = list(map(lambda x: x * 2, numeros))
dobles_comp = [x * 2 for x in numeros]           # más pythónico

pares_filter = list(filter(lambda x: x % 2 == 0, numeros))
pares_comp   = [x for x in numeros if x % 2 == 0]

Preferí comprehensions para casos simples. Usá map/filter cuando la función ya existe (evitás una lambda).

20. ¿Qué es `itertools` y para qué sirve?

python
import itertools

# chain — concatenar iterables
list(itertools.chain([1, 2], [3, 4]))   # => [1, 2, 3, 4]

# product — producto cartesiano
list(itertools.product("AB", [1, 2]))   # => [('A',1),('A',2),('B',1),('B',2)]

# islice — slice de generador infinito
list(itertools.islice(itertools.count(1), 5))  # => [1, 2, 3, 4, 5]

# groupby — agrupar elementos consecutivos
from itertools import groupby
datos = [("a", 1), ("a", 2), ("b", 3)]
for key, grupo in groupby(datos, key=lambda x: x[0]):
    print(key, list(grupo))

Bloque 5: Manejo de errores y context managers

21. ¿Cómo funciona el manejo de excepciones en Python?

python
def dividir(a, b):
    try:
        resultado = a / b
    except ZeroDivisionError:
        return None
    except TypeError as e:
        raise   # re-lanza la excepción original
    else:
        return resultado   # SOLO si no hubo excepciones
    finally:
        pass   # se ejecuta SIEMPRE — limpiar recursos aquí

# Excepciones propias
class SaldoInsuficienteError(ValueError):
    def __init__(self, saldo, monto):
        super().__init__(f"Saldo {saldo} insuficiente para retirar {monto}")

22. ¿Qué es un context manager y cómo se crea uno?

python
class ConexionDB:
    def __enter__(self):
        self.conexion = "conexion_simulada"
        return self.conexion

    def __exit__(self, tipo_exc, valor_exc, traceback):
        print("Desconectando...")
        return False  # propaga la excepción

with ConexionDB() as conn:
    print(f"Usando: {conn}")

# Con contextlib — más simple
from contextlib import contextmanager

@contextmanager
def tempdir():
    import tempfile, shutil
    d = tempfile.mkdtemp()
    try:
        yield d
    finally:
        shutil.rmtree(d)   # siempre se limpia

Bloque 6: Programación funcional

23. ¿Qué son `zip()`, `enumerate()` y `any()`/`all()`?

python
nombres = ["Ana", "Carlos", "María"]
edades  = [25, 30, 28]

# zip — pares
agenda = dict(zip(nombres, edades))

# enumerate — índice + valor
for i, nombre in enumerate(nombres, start=1):
    print(f"{i}. {nombre}")

# any / all
numeros = [1, 3, 5, 4, 7]
print(any(n % 2 == 0 for n in numeros))  # True
print(all(n > 0 for n in numeros))        # True

24. ¿Qué es `functools.reduce()`?

python
from functools import reduce

producto = reduce(lambda acc, x: acc * x, [1, 2, 3, 4, 5])
# => 120

suma_con_inicial = reduce(lambda acc, x: acc + x, [1, 2, 3], 100)
# => 106

25. ¿Qué es `functools.lru_cache`?

lru_cache memoiza resultados para evitar recálculos costosos.

python
from functools import lru_cache, cache

@lru_cache(maxsize=128)
def fibonacci(n):
    if n < 2: return n
    return fibonacci(n - 1) + fibonacci(n - 2)

# Sin cache: O(2^n) | Con cache: O(n)
print(fibonacci(100))       # instantáneo
print(fibonacci.cache_info())  # hits, misses, currsize

@cache  # Python 3.9+ — sin límite
def factorial(n):
    return n * factorial(n - 1) if n else 1

Bloque 7: Tipado estático y type hints

26. ¿Qué son los type hints y cómo se usan?

python
from typing import Optional, List, Dict, Tuple

def procesar(
    nombres: List[str],
    config: Optional[Dict[str, int]] = None,
) -> Tuple[int, List[str]]:
    if config is None:
        config = {}
    filtrados = [n for n in nombres if len(n) > config.get("min_len", 0)]
    return len(filtrados), filtrados

# Python 3.10+ — union type más limpio
def validar(valor: int | str | None) -> bool:
    return valor is not None

27. ¿Qué es un Protocol en Python?

Los Protocol permiten duck typing estructural — interfaces sin herencia explícita.

python
from typing import Protocol, runtime_checkable

@runtime_checkable
class Serializable(Protocol):
    def to_dict(self) -> dict: ...
    def to_json(self) -> str: ...

class Usuario:
    def __init__(self, nombre: str):
        self.nombre = nombre

    def to_dict(self) -> dict:
        return {"nombre": self.nombre}

    def to_json(self) -> str:
        import json
        return json.dumps(self.to_dict())

# No hereda de Serializable, pero lo cumple
print(isinstance(Usuario("x"), Serializable))  # True

Bloque 8: Async/await y concurrencia

28. ¿Cuál es la diferencia entre threading, multiprocessing y asyncio?

  • threading — I/O-bound, librerías síncronas. El GIL bloquea CPU-bound.
  • multiprocessing — CPU-bound (cálculo pesado). Sin GIL compartido.
  • asyncio — I/O-bound con alta concurrencia. No hay paralelismo real.
python
import asyncio

async def fetch(session, url):
    async with session.get(url) as response:
        return await response.json()

async def main():
    import aiohttp
    urls = ["https://api.ejemplo.com/1", "https://api.ejemplo.com/2"]
    async with aiohttp.ClientSession() as session:
        resultados = await asyncio.gather(*[fetch(session, u) for u in urls])
    return resultados

asyncio.run(main())

29. ¿Qué es el GIL y cómo afecta al código Python?

El Global Interpreter Lock (GIL) permite que solo un thread ejecute bytecode CPython a la vez.

python
import multiprocessing

def calcular(n):
    return sum(range(n))

# Multiprocessing — SÍ paraleliza (procesos separados, sin GIL compartido)
with multiprocessing.Pool(4) as pool:
    resultados = pool.map(calcular, [10**7] * 4)

# concurrent.futures — API más simple
from concurrent.futures import ProcessPoolExecutor
with ProcessPoolExecutor(max_workers=4) as ex:
    resultados = list(ex.map(calcular, [10**7] * 4))

30. ¿Cómo funcionan las coroutines y `async/await`?

python
import asyncio

async def tarea(nombre, segundos):
    print(f"{nombre} iniciando")
    await asyncio.sleep(segundos)   # cede el control al event loop
    print(f"{nombre} terminó")
    return f"resultado de {nombre}"

async def main():
    # Secuencial: 3s | Concurrente con gather: ~2s
    r1, r2 = await asyncio.gather(
        tarea("A", 1),
        tarea("B", 2),
    )
    print(r1, r2)

asyncio.run(main())

Bloque 9: Módulos y paquetes

31. ¿Cuál es la diferencia entre un módulo y un paquete?

myapp/
  __init__.py        # convierte el directorio en paquete
  models/
    __init__.py
    usuario.py
  utils.py
python
import myapp.utils
from myapp.models.usuario import Usuario

# En __init__.py podés controlar qué se exporta:
# from .models.usuario import Usuario
# __all__ = ["Usuario"]

32. ¿Qué hace `if __name__ == "__main__"`?

python
def sumar(a, b):
    return a + b

# Solo se ejecuta cuando el archivo es el punto de entrada
# NO se ejecuta cuando se importa como módulo
if __name__ == "__main__":
    print(sumar(3, 4))   # 7

Bloque 10: Estructuras de datos y algoritmos

33. ¿Cómo invertís una lista sin usar `reverse()`?

python
lista = [1, 2, 3, 4, 5]

# Slicing — crea nueva lista
invertida = lista[::-1]

# In-place sin reverse() — O(n) tiempo, O(1) espacio
izq, der = 0, len(lista) - 1
while izq < der:
    lista[izq], lista[der] = lista[der], lista[izq]
    izq += 1
    der -= 1

34. ¿Cómo encontrás el elemento más frecuente en una lista?

python
from collections import Counter

datos = [1, 3, 2, 1, 3, 1, 4, 3, 2, 1]
conteo = Counter(datos)
print(conteo.most_common(1)[0][0])   # 1
print(conteo.most_common(3))         # [(1, 4), (3, 3), (2, 2)]

# Sin Counter
def mas_frecuente(lista):
    freq = {}
    for x in lista:
        freq[x] = freq.get(x, 0) + 1
    return max(freq, key=freq.get)

35. ¿Cómo implementás una pila y una cola en Python?

python
from collections import deque

# Pila (LIFO) — lista
pila = []
pila.append(1); pila.append(2)
tope = pila.pop()  # O(1)

# Cola (FIFO) — deque, NO lista (list.pop(0) es O(n))
cola = deque()
cola.append(1); cola.append(2)
frente = cola.popleft()  # O(1)

# Cola de prioridad
import heapq
heap = []
heapq.heappush(heap, 3)
heapq.heappush(heap, 1)
heapq.heappush(heap, 2)
print(heapq.heappop(heap))  # 1 — siempre el menor

36. ¿Cómo implementás una búsqueda binaria?

python
def busqueda_binaria(arr, objetivo):
    izq, der = 0, len(arr) - 1
    while izq <= der:
        mid = (izq + der) // 2
        if arr[mid] == objetivo:
            return mid
        elif arr[mid] < objetivo:
            izq = mid + 1
        else:
            der = mid - 1
    return -1

arr = [1, 3, 5, 7, 9, 11, 13]
print(busqueda_binaria(arr, 7))   # => 3

# stdlib
import bisect
print(bisect.bisect_left(arr, 7))  # 3

37. ¿Cómo detectás si una cadena es palíndromo?

python
def es_palindromo(s: str) -> bool:
    s = "".join(c.lower() for c in s if c.isalnum())
    return s == s[::-1]

# Con dos punteros — O(1) espacio extra
def es_palindromo_v2(s: str) -> bool:
    s = "".join(c.lower() for c in s if c.isalnum())
    izq, der = 0, len(s) - 1
    while izq < der:
        if s[izq] != s[der]:
            return False
        izq += 1; der -= 1
    return True

print(es_palindromo("A man a plan a canal Panama"))  # True
print(es_palindromo("race a car"))                    # False

Bloque 11: Testing

38. ¿Cómo escribís tests con `pytest`?

python
import pytest

def sumar(a, b):
    return a + b

def test_sumar():
    assert sumar(2, 3) == 5

@pytest.mark.parametrize("a,b,esperado", [
    (2, 3, 5), (-1, 1, 0), (100, -50, 50),
])
def test_sumar_param(a, b, esperado):
    assert sumar(a, b) == esperado

def test_excepcion():
    with pytest.raises(ValueError, match="División por cero"):
        if 0 == 0: raise ValueError("División por cero")

@pytest.fixture
def usuario_base():
    return {"nombre": "Ana", "saldo": 1000}

def test_usuario(usuario_base):
    assert usuario_base["saldo"] == 1000

39. ¿Qué es mocking y cuándo se usa?

python
from unittest.mock import patch, MagicMock

def obtener_precio(pid: str) -> float:
    import requests
    return requests.get(f"https://api.precios.com/{pid}").json()["precio"]

def calcular_total(pid: str, cantidad: int) -> float:
    return obtener_precio(pid) * cantidad

@patch("__main__.obtener_precio")
def test_calcular_total(mock_precio):
    mock_precio.return_value = 10.0
    assert calcular_total("ABC", 3) == 30.0
    mock_precio.assert_called_once_with("ABC")

Bloque 12: Patrones avanzados

40. ¿Qué es el patrón Singleton en Python?

python
class SingletonMeta(type):
    _instancias = {}

    def __call__(cls, *args, **kwargs):
        if cls not in cls._instancias:
            cls._instancias[cls] = super().__call__(*args, **kwargs)
        return cls._instancias[cls]

class Config(metaclass=SingletonMeta):
    def __init__(self):
        self.debug = False

c1 = Config()
c2 = Config()
print(c1 is c2)  # True

41. ¿Cómo implementás el patrón Observer?

python
from typing import Callable, List

class EventEmitter:
    def __init__(self):
        self._listeners: dict[str, List[Callable]] = {}

    def on(self, evento: str, callback: Callable):
        self._listeners.setdefault(evento, []).append(callback)
        return self

    def emit(self, evento: str, *args, **kwargs):
        for cb in self._listeners.get(evento, []):
            cb(*args, **kwargs)

emitter = EventEmitter()
emitter.on("usuario.creado", lambda u: print(f"Bienvenido {u}"))
emitter.on("usuario.creado", lambda u: print(f"Email enviado a {u}"))
emitter.emit("usuario.creado", "Ana")

42. ¿Qué es una metaclass en Python?

python
class ValidadorAtributos(type):
    def __new__(mcs, nombre, bases, namespace):
        for attr in namespace:
            if not attr.startswith("_") and not attr.isupper():
                raise ValueError(f"{attr!r} debe estar en MAYÚSCULAS")
        return super().__new__(mcs, nombre, bases, namespace)

# Uso más común: ABCs
from abc import ABC, abstractmethod

class Figura(ABC):
    @abstractmethod
    def area(self) -> float: ...

    @abstractmethod
    def perimetro(self) -> float: ...

43. ¿Qué es un descriptor en Python?

python
class Tipado:
    def __init__(self, tipo):
        self.tipo = tipo; self.nombre = None

    def __set_name__(self, owner, nombre):
        self.nombre = nombre

    def __get__(self, obj, objtype=None):
        if obj is None: return self
        return obj.__dict__.get(self.nombre)

    def __set__(self, obj, valor):
        if not isinstance(valor, self.tipo):
            raise TypeError(f"{self.nombre} debe ser {self.tipo.__name__}")
        obj.__dict__[self.nombre] = valor

class Persona:
    nombre = Tipado(str)
    edad   = Tipado(int)

    def __init__(self, nombre, edad):
        self.nombre = nombre
        self.edad = edad

Bloque 13: Rendimiento y optimización

44. ¿Cómo medís y optimizás el rendimiento de Python?

python
import timeit

# join es ~10x más rápido que concatenación con +
timeit.timeit('"".join(palabras)', setup='palabras=["hola"]*1000', number=1000)

import cProfile
cProfile.run("funcion_lenta()")

# __slots__ reduce memoria en clases con muchas instancias
class PuntoOptimizado:
    __slots__ = ("x", "y")   # evita __dict__ por instancia
    def __init__(self, x, y):
        self.x = x; self.y = y

45. ¿Cuándo usás `numpy` sobre listas?

python
import numpy as np

arr = np.arange(1_000_000)

# Suma de cuadrados: Python puro ~200ms | NumPy ~2ms
resultado = np.sum(arr ** 2)

# Broadcasting — sin loops
a = np.array([[1, 2, 3], [4, 5, 6]])   # shape (2, 3)
b = np.array([10, 20, 30])              # shape (3,)
c = a + b                               # broadcast: (2, 3)

# Indexing booleano
datos = np.array([1, -2, 3, -4, 5])
positivos = datos[datos > 0]   # => [1, 3, 5]

Bloque 14: Pythonismos y best practices

46. ¿Cuáles son los principales principios de código pythónico?

python
# Intercambio sin variable temporal
a, b = b, a

# Unpacking extendido
primero, *resto    = [1, 2, 3, 4, 5]
*inicio, ultimo    = [1, 2, 3, 4, 5]
primero, *m, ult   = [1, 2, 3, 4, 5]

# Walrus operator (Python 3.8+)
import re
if m := re.search(r"\d+", "hay 42 errores"):
    print(m.group())   # "42"

# EAFP — más pythónico
try:
    valor = diccionario["key"]
except KeyError:
    valor = default

# Concatenación eficiente
resultado = " ".join(["Hola", "mundo", "Python"])

47. ¿Qué es PEP 8 y cuáles son sus reglas más importantes?

python
mi_variable = 1          # snake_case para variables/funciones
MiClase = True           # PascalCase para clases
MI_CONSTANTE = 42        # UPPER_SNAKE para constantes
_privado = True          # _ para atributos privados (convención)

def funcion(a, b=0):     # sin espacio en default args
    pass

# Líneas largas con paréntesis
resultado = (
    muy_largo_nombre
    + otro_nombre
    + un_tercero
)

48. ¿Cómo usás `collections.defaultdict` y `collections.namedtuple`?

python
from collections import defaultdict, namedtuple

# defaultdict — evita KeyError
conteo = defaultdict(int)
for palabra in "el gato come el pescado".split():
    conteo[palabra] += 1

agrupado = defaultdict(list)
for k, v in [("A", 1), ("B", 2), ("A", 3)]:
    agrupado[k].append(v)
# => {"A": [1, 3], "B": [2]}

# namedtuple — tupla con nombres
Punto = namedtuple("Punto", ["x", "y"])
p = Punto(3, 4)
print(p.x, p.y)   # acceso por nombre
x, y = p          # unpacking también funciona

49. ¿Qué son las variables de entorno y cómo se manejan en Python?

python
import os
from pathlib import Path

db_url = os.environ.get("DATABASE_URL", "sqlite:///local.db")
debug  = os.environ.get("DEBUG", "false").lower() == "true"

# pathlib — manejo moderno de rutas
ruta    = Path("/home/usuario/proyecto")
archivo = ruta / "datos" / "config.json"

if archivo.exists():
    contenido = archivo.read_text(encoding="utf-8")

for py_file in ruta.glob("**/*.py"):
    print(py_file)

(ruta / "output").mkdir(parents=True, exist_ok=True)

50. ¿Cómo estructurás un proyecto Python profesional?

La estructura recomendada usa src layout con pyproject.toml:

mi_proyecto/
├── src/
│   └── mi_paquete/
│       ├── __init__.py
│       ├── models.py
│       ├── services.py
│       └── utils.py
├── tests/
│   ├── test_models.py
│   └── test_services.py
├── pyproject.toml
├── .env.example
└── README.md
toml
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[project]
name = "mi-paquete"
version = "0.1.0"
dependencies = ["requests>=2.31", "pydantic>=2.0"]

[tool.pytest.ini_options]
testpaths = ["tests"]

[tool.mypy]
strict = true

Cómo usar esta guía para preparar tu entrevista

Semana antes: repasá los bloques de fundamentos (1-5), funciones (6-10) y OOP (11-15).

Día anterior: bloques de generadores, async/await y algoritmos (16-37).

La mañana de la entrevista: repasá los pythonismos (46-50) — son los que los entrevistadores esperan que domines naturalmente.

En la entrevista: cuando recibas un ejercicio, pensá en voz alta. Los entrevistadores evalúan tu proceso tanto como la solución final. La práctica más efectiva es escribir el código de memoria, no leerlo — cerrá esta página y reescribí las funciones que te resultaron menos claras.

FAQ

¿Cuáles son las preguntas más frecuentes en entrevistas de Python?+

Las más frecuentes son: diferencia entre lista, tupla y set; cómo funciona el GIL; qué son los generadores y cómo se diferencian de las listas; decoradores y closures; manejo de excepciones; y preguntas de algoritmia como búsqueda binaria o detectar palíndromos.

¿Necesito saber async/await para una entrevista de Python backend?+

Depende del rol. Para roles backend con FastAPI, Django Channels o cualquier stack moderno, sí es esperado. Para roles de data science o scripting, es menos crítico. Al menos debés poder explicar la diferencia entre threading, multiprocessing y asyncio.

¿Me preguntan sobre el GIL en entrevistas técnicas?+

Sí, especialmente en roles de backend o infraestructura. El concepto clave es: el GIL impide el paralelismo real de threads en CPU-bound tasks, por eso se usa multiprocessing para cálculo pesado y threading o asyncio para I/O-bound.

¿Qué nivel de Python se evalúa en entrevistas de empresas tech internacionales?+

Esperan que domines fluentemente los fundamentos (tipos de datos, OOP, funciones de orden superior), que conozcas la stdlib (collections, itertools, functools), y que puedas escribir código pythónico sin pensar. Los generadores, decoradores y type hints son diferenciales.

¿Tengo que memorizar la complejidad algorítmica de las operaciones de Python?+

Sí, las más importantes: list.append() es O(1) amortizado, list.pop(0) es O(n) (usá deque), dict/set lookup es O(1) promedio. Conocer cuándo usar qué estructura de datos es tan importante como el algoritmo en sí.

¿Qué es lo más importante para aprobar una entrevista técnica de Python?+

Tres cosas: escribir código limpio y legible (PEP 8, nombres claros), pensar en voz alta mostrando tu razonamiento, y conocer las estructuras de datos de la stdlib. Los entrevistadores prefieren candidatos que reconocen cuándo usar Counter, defaultdict o heapq antes que reimplementarlos desde cero.

Related articles

Cómo usar el método STAR en entrevistas (con ejemplos reales)

Aprende a responder preguntas difíciles usando el método STAR en entrevistas. Consejos y ejemplos concretos para roles remotos tech de LATAM.

Cómo conseguir trabajo remoto en dólares desde LATAM: guía real

Descubre consejos concretos para conseguir trabajo remoto en dólares desde LATAM: estrategias de búsqueda, preparación y entrevista para roles tecnológicos.

Las mejores preguntas para hacerle al entrevistador al final

Descubre las mejores preguntas para hacerle al entrevistador al final, útiles para entrevistas tech remotas, diferenciándote y logrando roles en dólares.

Cómo preparar entrevistas de desarrollo sin experiencia previa

Consejos prácticos para enfrentar entrevistas de tu primer trabajo como desarrollador, incluso sin experiencia. Técnicas para destacar y convencer en cada etapa.

Prepare for your real interview

Paste your job link: we research who's interviewing you and rehearse you live.

Start free →

Have an interview coming up? Install the live copilot →

InterviewHack.ai

Prepare for the exact interview: who's interviewing you, a tailored CV, and a real coach.

Product

JobsFree ATS checkerInterview-English checkSalary checkLATAM salary reportFree coursesBlogTailored CVSpoken practiceIt's free

Remote jobs

ReactPythonFull-StackLATAMArgentinaMexicoSee all →

Prepare

Spoken practiceFrontendBackendAI EngineerBy companySell with your CV

Company

For employersAboutContactPrivacyTerms

© 2026 InterviewHack.ai · Your CV is yours. Never used to train anything. · A product of IA-PTY