Resumen

Un feature flag es un condicional en el código cuyo valor se controla desde afuera del código (un archivo de config, una base de datos o un servicio). Permite prender o apagar una funcionalidad en producción sin hacer un nuevo deploy, y elegir para quién se prende.

Analogía: la instalación eléctrica

El electricista deja el cable y la lámpara instalados, pero la luz se prende recién cuando alguien baja el interruptor. El deploy es la instalación; el flag es el interruptor.

Key points

  • Separa deploy (subir código) de release (que los usuarios lo vean)
  • Un rollback pasa a ser apagar un flag: segundos en vez de un redeploy
  • Permite prender una funcionalidad para un porcentaje o un grupo de usuarios
  • Cada flag es deuda técnica: hay que borrarlo cuando ya no hace falta

Deploy ≠ release

Sin flags, desplegar y lanzar son lo mismo: si el código está en producción, los usuarios lo ven. Con flags, el código nuevo llega a producción apagado y se lanza después, de a poco.

flowchart LR
    A[Merge a main] --> B["Deploy a producción<br/>(flag OFF)"]
    B --> C[Prender para 1% de usuarios]
    C --> D{¿Métricas bien?}
    D -->|sí| E[10% → 50% → 100%]
    D -->|no| F["Apagar el flag<br/>(sin redeploy)"]
    E --> G[Borrar el flag del código]

Esto habilita el Trunk-based development: se puede mergear a main código a medio terminar, porque queda escondido detrás de un flag apagado. Nada de ramas que viven semanas y terminan en merges dolorosos.

Tipos de flags

La clasificación más usada (Pete Hodgson, en martinfowler.com) los separa por para qué sirven:

TipoPara quéVida útilQuién lo cambia
ReleaseEsconder código sin terminar o lanzar de a pocoDías o semanasDevs
ExperimentA-B testing: mostrar variantes distintas y medir cuál funciona mejorSemanasProducto / data
OpsControlar el sistema en producción. El caso típico es el kill switch: apagar algo pesado si el sistema está sobrecargadoVariable, a veces permanenteOps / SRE
PermissionDar acceso a ciertos usuarios (plan premium, beta testers, admins)Largo plazoNegocio

Dos ejes ayudan a ubicarlos: cuánto viven y qué tan dinámica es la decisión (¿se decide una vez por deploy o en cada request según quién es el usuario?).

quadrantChart
    title Tipos de feature flags
    x-axis Vida corta --> Vida larga
    y-axis "Decisión estática" --> "Decisión por request"
    quadrant-1 "Larga y dinámica"
    quadrant-2 "Corta y dinámica"
    quadrant-3 "Corta y estática"
    quadrant-4 "Larga y estática"
    Release: [0.2, 0.25]
    Experiment: [0.35, 0.85]
    Ops: [0.65, 0.45]
    Permission: [0.85, 0.9]

Por qué importa el tipo

Cada tipo se gestiona distinto. Un release flag debería desaparecer en semanas; un permission flag es parte del producto y vive para siempre. Mezclarlos es como terminan los repos con cientos de flags que nadie se anima a borrar.

Cómo se evalúa un flag

Para decidir si un flag está prendido para un usuario, el sistema recorre reglas en orden:

flowchart TD
    A["is_enabled(flag, usuario)"] --> B{¿El flag existe<br/>y está activo?}
    B -->|no| OFF1[OFF: default seguro]
    B -->|sí| C{¿El usuario está<br/>en la lista de permitidos?}
    C -->|sí| ON1[ON]
    C -->|no| D{"hash(flag + usuario) % 100<br/>&lt; porcentaje?"}
    D -->|sí| ON2[ON]
    D -->|no| OFF2[OFF]

¿Por qué un hash y no random()?

Con random() el mismo usuario vería la funcionalidad nueva en un request y la vieja en el siguiente. El hash del ID del usuario siempre da el mismo número, así que cada usuario cae siempre en el mismo lado. Y al incluir el nombre del flag en el hash, no son siempre los mismos usuarios los que prueban todo lo nuevo.

Ejemplo mínimo (Python)

import hashlib
 
FLAGS = {
    "nuevo-checkout": {
        "enabled": True,
        "rollout_percentage": 20,
        "allowed_users": {"beta-tester-1"},
    }
}
 
def bucket(flag_name: str, user_id: str) -> int:
    """Asigna al usuario un número estable entre 0 y 99 para este flag"""
    digest = hashlib.sha256(f"{flag_name}:{user_id}".encode()).hexdigest()
    return int(digest, 16) % 100
 
def is_enabled(flag_name: str, user_id: str) -> bool:
    flag = FLAGS.get(flag_name)
    if flag is None or not flag["enabled"]:
        return False  # default seguro: si algo falla, queda apagado
    if user_id in flag["allowed_users"]:
        return True
    return bucket(flag_name, user_id) < flag["rollout_percentage"]
 
def checkout(user_id: str, carrito):
    if is_enabled("nuevo-checkout", user_id):
        return nuevo_checkout(carrito)
    return checkout_actual(carrito)

En un sistema real, FLAGS no está hardcodeado: vive en una base de datos o en un servicio de flags, y el SDK lo cachea en memoria para no hacer una llamada de red en cada is_enabled.

Herramientas

HerramientaTipo
LaunchDarklySaaS, el más conocido del mercado
UnleashOpen source, se puede self-hostear
FlagsmithOpen source + SaaS
GrowthBookOpen source, enfocado en experimentos (A/B testing)
OpenFeatureNo es una herramienta sino un estándar (proyecto de la CNCF): una API común para evaluar flags, con “providers” para cada herramienta. Si cambiás de LaunchDarkly a Unleash, el código de la app no cambia

Ventajas y desventajas

  • Rollback instantáneo: apagar un flag en vez de revertir y redesplegar
  • Lanzamientos progresivos: probar con pocos usuarios antes de exponer a todos
  • Permite mergear seguido a main sin exponer código incompleto
  • Habilita experimentos y acceso por plan o por grupo de usuarios
  • Cada flag duplica los caminos posibles del código: con n flags hay 2ⁿ combinaciones para testear
  • Los flags viejos se acumulan y el código se llena de if muertos
  • Agrega una dependencia más: si el servicio de flags se cae, la app tiene que saber qué hacer

Riesgos y buenas prácticas

Flags en el frontend

Todo lo que llega al navegador el usuario lo puede ver y modificar. Un flag evaluado en el cliente sirve para mostrar u ocultar UI, pero nunca para controlar permisos: eso se valida siempre en el backend.

  • Default seguro: si el servicio de flags no responde, el flag tiene que caer en el comportamiento conocido (casi siempre, apagado)
  • Nombres descriptivos: nuevo-checkout-2026 dice más que flag_17
  • Dueño y fecha de vencimiento: cada release flag debería tener un responsable y una fecha para borrarlo
  • Testear las dos ramas: tanto con el flag prendido como apagado
  • Observabilidad: registrar qué valor tuvo cada flag en cada request, para poder cruzarlo con errores y métricas (Observabilidad)
  • Borrarlo al terminar: cuando el rollout llegó al 100% y está estable, se elimina el flag y la rama vieja del código

Relación con otras estrategias de deploy

Los flags se combinan con, pero no reemplazan a, las estrategias de deploy de infraestructura:

EstrategiaQué controlaNivel
Feature flagQué funcionalidad ve cada usuarioCódigo
Canary deploymentQué versión de la app recibe una parte del tráficoInfraestructura
Blue-green deploymentCambiar todo el tráfico de una versión a otra de golpeInfraestructura

Un canary deployment prueba una versión entera; un flag prueba una funcionalidad puntual dentro de una versión.