Tema representativo de entrevista

Entrevista Frontend: ¿Cómo implementar Optimistic UI de forma segura?

FrontendDifícil
Equipo editorial de Offer.ccPublicado Actualizado

Pregunta

Un editor de tareas muestra un título guardado de inmediato, pero los usuarios pueden enviar A y luego B antes de que termine cualquiera de las solicitudes. Las respuestas pueden llegar desordenadas, cualquiera de los guardados puede fallar y otro usuario puede editar la tarea. ¿Cómo diseñarías el estado optimista, el ordenamiento de mutaciones, el rollback, la reconciliación, la retroalimentación accesible y las pruebas para que la UI y el servidor no diverjan silenciosamente?

Prompt y escenarios aplicables

Un editor de tareas muestra de inmediato un título enviado. Un usuario puede enviar el título A y luego el título B antes de que termine cualquiera de las solicitudes. La segunda respuesta puede llegar primero, cualquiera de las solicitudes puede fallar, un refetch en segundo plano puede completarse mientras ambas están pendientes y otro usuario puede actualizar la misma tarea. El servidor devuelve la tarea canónica y una versión de recurso que se incrementa monótonamente después de cada escritura aceptada.

Diseña el estado del cliente, el contrato de solicitud, la política de ordenamiento, la recuperación ante fallos, la experiencia de conflictos, la retroalimentación de accesibilidad y el plan de validación. El requisito no es simplemente hacer que la interfaz se sienta rápida. Después de cada orden de finalización, el estado visible debe ser explicable a partir de un estado de servidor autoritativo más las intenciones aún pendientes del usuario.

Esta pregunta se adapta a entrevistas de frontend senior, infraestructura de UI y diseño de sistemas frontend. El material público de entrevistas frontend cubre explícitamente actualizaciones optimistas, carreras de solicitudes (request races), estados de error y rollback, mientras que un registro de entrevista pública de 2025 describe preguntas de seguimiento sobre principios y casos de uso de actualizaciones optimistas. Esas fuentes respaldan la relevancia actual del tema; no establecen una pregunta específica de una empresa ni la frecuencia de las entrevistas. La categoría es frontend porque la tarea central es el estado asíncrono y el diseño de interacción en el lado del navegador. El control de concurrencia del servidor es una entrada para ese diseño, no el objetivo de implementación principal.

Lo que evalúa el entrevistador

La primera señal es si el candidato separa el estado autoritativo del estado especulativo. Reemplazar un objeto en una caché y guardar una instantánea (snapshot) antigua para el rollback funciona para una solicitud aislada. Falla cuando el trabajo optimista posterior depende del mismo objeto. Un modelo robusto mantiene la base confirmada más reciente y una colección ordenada de intenciones pendientes, y luego deriva la vista renderizada a partir de ambas.

La segunda señal es la semántica de la mutación. "Mantener solo la última respuesta" protege una ruta de renderizado del cliente, pero no puede evitar que una solicitud más antigua sea procesada en último lugar por el servidor. El candidato debe decidir si las operaciones se serializan, se fusionan (coalesced), se hacen conmutativas o se les asigna un orden impuesto por el servidor. La elección cambia para "establecer título a B", "incrementar en uno", "alternar (toggle)", "eliminar" y un comando de pago.

La tercera señal es la recuperación selectiva. Cuando la operación A falla después de que se envió la operación B, restaurar la instantánea anterior a A puede borrar B. Una respuesta sólida elimina o marca la operación fallida, avanza la base confirmada solo a partir de una respuesta autoritativa válida y repite (replays) las intenciones restantes. Si un conflicto de versión del servidor hace que la repetición sea insegura, la UI muestra el valor actual del servidor y solicita una resolución deliberada.

La señal final es la disciplina de producción: los estados pendientes y de error siguen siendo operables con el teclado y tecnologías de asistencia; la cancelación no se confunde con un rollback en el servidor; y las pruebas fuerzan cada orden de respuesta, orden de fallo, carrera de refetch, reintento y conflicto en lugar de validar únicamente el camino feliz.

Preguntas para clarificar antes de responder

  • ¿Qué significa una operación? Un setTitle("B") absoluto puede sustituir a un borrador de título anterior, mientras que increment(1) puede requerir que ambas operaciones se confirmen (commit). Un comando toggle() es ambiguo bajo reintentos; un valor objetivo explícito es más seguro. La semántica de la operación determina si la fusión (coalescing) es válida.
  • ¿Puede el usuario volver a enviar mientras un guardado está pendiente? Deshabilitar el control proporciona una serialización simple, pero puede perjudicar la experiencia de edición. Si se requiere una entrada continua, preserva el borrador local por separado y encola/fusiona los envíos o utiliza un protocolo paralelo versionado.
  • ¿Qué sistema decide el orden de escritura? Si el servidor solo ofrece escrituras incondicionales donde la última llegada gana (last-arrival-wins), el cliente debe serializar los guardados dependientes del orden. Si la API acepta una versión base o una secuencia de cliente y rechaza el trabajo obsoleto, el paralelismo controlado se vuelve posible.
  • ¿La acción optimista es reversible y de bajo riesgo? Los "me gusta", las etiquetas y los borradores a menudo se adaptan a la retroalimentación optimista. Los pagos, las acciones destructivas, los cambios sensibles a permisos y las acciones con efectos externos irreversibles pueden requerir confirmación o un estado pendiente en lugar de fingir el éxito.
  • ¿Puede una fuente en segundo plano actualizar el mismo registro? Los refetches, las suscripciones, otra pestaña del navegador y los colaboradores pueden cambiar la base. El modelo de estado debe identificar una versión de recurso y definir si las intenciones pendientes se pueden reaplicar de forma segura sobre una base más nueva.
  • ¿Qué debe percibir el usuario? Clarifica si una fila individual necesita indicadores de pendiente, reintento y conflicto; si el foco puede moverse; y qué mensajes de guardado o fallo deben anunciarse sin producir un mensaje de live-region para cada pulsación de tecla.

Estructura de respuesta en 30 segundos

“Mantendría la última tarea confirmada por el servidor como base y representaría cada envío local como una intención identificada. La UI renderiza las intenciones pendientes sobre esa base. Para el reemplazo de títulos, mi valor predeterminado es un guardado en curso por tarea y fusionar los borradores en cola al título más reciente; un ID de solicitud por sí solo no puede evitar que el servidor aplique una solicitud antigua en último lugar. En caso de éxito, adopto la versión canónica devuelta, elimino esa intención y repito cualquier intención restante. En caso de fallo, elimino solo la intención fallida, en lugar de restaurar una instantánea obsoleta completa. Un conflicto de versiones pausa la repetición automática y muestra ambos valores. Expondría el estado pendiente y de error por elemento, preservaría el foco y probaría respuestas reordenadas, éxitos y fallos mixtos, carreras de refetch, reintentos, conflictos y recuperación sin conexión.”

Análisis detallado paso a paso

1. Definir una invariante antes de elegir una biblioteca

Para cada recurso, mantén un base confirmado y una lista pending ordenada. Cada entrada pendiente tiene un ID de operación de cliente estable, la intención y el payload, su orden de envío y su estado actual. El estado mostrado es una proyección pura:

text
view = fold(base, pending in logical order, applyIntent)

La invariante es: la vista renderizada es igual al estado más reciente aceptado por el servidor más cada intención local que aún sea elegible para aplicarse. Este modelo hace que un refetch o una respuesta tardía sea una entrada para la reconciliación en lugar de una instrucción para sobrescribir la pantalla.

El patrón de reductor optimista de React admite la misma separación: cuando el valor base cambia mientras una Action está pendiente, React puede volver a ejecutar el reductor contra la nueva base. Una biblioteca de caché puede gestionar callbacks del ciclo de vida de las mutaciones, pero no elige la semántica de operaciones del producto. La invariante sigue siendo útil tanto si la implementación utiliza el estado de React, TanStack Query, otra caché de cliente o una tienda personalizada (custom store).

No mantengas tres copias no relacionadas llamadas serverTask, formTask y optimisticTask con efectos de sincronización ad hoc. Mantén el borrador del formulario no enviado separado porque escribir aún no es una mutación. Una vez enviado, conviértelo en una intención con una identidad estable.

2. Elegir el ordenamiento a partir de la operación, no de la latencia

Existen tres políticas útiles:

PolíticaCaso adecuadoCosto o riesgo
Serializar por recursoEscrituras dependientes del orden; la API no tiene protección de ordenamientoEl trabajo posterior espera, pero el orden en el servidor es demostrable
Serializar y fusionar (coalesce)Solo importa el último valor no enviado, como envíos repetidos de títulosLos valores enviados intermedios se descartan intencionalmente
Paralelo con contrato de servidorOperaciones independientes o conmutativas, o la API impone versión base/secuencia de clienteMayor rendimiento, pero la reconciliación y los conflictos son explícitos

Para este editor de títulos, serializa por ID de tarea y fusiona los cambios de título no enviados en cola. Si A está en curso y se envía B, muestra B de forma optimista pero mantén solo B como la próxima escritura de red. Después de que A se resuelva, envía B contra la versión aceptada más reciente. TanStack Query documenta que las mutaciones de otro modo se ejecutan en paralelo y proporciona ámbitos de mutación (mutation scopes) para la ejecución en serie; la misma política se puede implementar sin esa biblioteca.

Las solicitudes paralelas son seguras solo con semánticas más fuertes. Una API puede rechazar una versión base obsoleta, aceptar una secuencia de cliente monótonamente creciente para una sesión de edición o exponer una operación que sea genuinamente conmutativa. La documentación de React también advierte que las Transitions asíncronas personalizadas no garantizan el orden de las solicitudes; todavía se requiere una Action ordenada de nivel superior o una cola explícita. Rastrear el ID de solicitud más nuevo únicamente en el navegador evita que una respuesta antigua sobrescriba la B visible, pero el servidor aún puede almacenar A en último lugar. Abortar A tampoco demuestra que el servidor no la haya confirmado.

3. Reconciliar el éxito sin confiar en el orden de llegada

Cada respuesta debe identificar la operación y devolver el recurso canónico más su versión. Con la serialización, maneja la operación activa, adopta la base devuelta, elimina la operación y luego deriva la vista repitiendo la intención en cola. La B en cola permanece visible mientras A se completa, por lo que la pantalla no salta hacia atrás a A.

Con paralelismo controlado, haz coincidir una respuesta con su ID de operación y valida su versión de recurso o confirmación (acknowledgement). No asignes datos de respuesta directamente solo porque la promesa se resolvió. Una respuesta más antigua que la versión confirmada actual no puede reemplazar la base. Elimina únicamente las operaciones que la respuesta realmente reconozca. Si el servidor devuelve una transformación canónica, como recortar espacios en un título, utiliza ese resultado como la nueva base antes de repetir las intenciones locales posteriores.

Un refetch en segundo plano sigue la misma regla. Si devuelve la versión 12 mientras la base actual es la versión 11, la versión 12 puede avanzar la base; las intenciones pendientes se vuelven a aplicar si su semántica lo permite. Un refetch con la versión 10 es evidencia obsoleta y no puede mover la base hacia atrás.

4. Recuperar por operación, no por instantánea

Supongamos que A y B son visibles optimistamente y luego A falla. Restaurar el objeto capturado antes de A elimina B como daño colateral. En su lugar, marca A como fallida o elimínala, conserva B y vuelve a calcular la proyección. Para una asignación de título absoluta, B aún se puede enviar contra la base actual. Para un delta dependiente del orden, B puede necesitar esperar, ser recalculada o ser rechazada porque su premisa original ya no se cumple.

Separa los fallos en estados accionables:

  • Un fallo de validación o permiso es definitivo hasta que el usuario cambie la entrada o el acceso; muestra el valor rechazado y el motivo del servidor cerca del control.
  • Un fallo de red transitorio puede exponer la opción de reintentar. Reutiliza la misma identidad de operación solo si el contrato del servidor hace que ese reintento sea seguro; de lo contrario, primero reconcilia si la escritura original se confirmó.
  • Un conflicto de versión significa que la base cambió en otro lugar. Adopta u obtén el valor actual del servidor, compáralo con la intención local y repite automáticamente solo una operación con una regla de combinación (merge) demostrada. Para un conflicto de títulos, presenta los valores actual y propuesto en lugar de seleccionar uno silenciosamente.
  • Un resultado desconocido significa que la solicitud puede haberse confirmado aunque la respuesta se haya perdido. “Hacer rollback localmente y reintentar como nuevo” puede duplicar una acción no idempotente.

Algunas acciones no deberían ser optimistas. Si un fallo fuera difícil de deshacer, cambia la autorización, cobra dinero o crea un estado legal o comercial engañoso, muestra una confirmación pendiente inmediata y confirma solo después de que el servidor la acepte.

5. Hacer que el estado especulativo sea visible y accesible

Optimista no significa indistinguible de confirmado. Marca la tarea afectada como guardando, retén el valor enviado y proporciona una acción local de reintento o resolución de conflicto. No deshabilites tareas no relacionadas. Si se utiliza la serialización, distingue el guardado activo de un valor en cola más nuevo para que la instrumentación y los mensajes de error se adjunten a la intención correcta.

Preserva el foco del teclado cuando un guardado tenga éxito, falle o la caché se reconcilie. Una región de estado puede anunciar "Guardando título de la tarea", "Título de la tarea guardado" o "Error al guardar" sin mover el foco. El rol status de W3C tiene semántica de live-region educada (polite); crea el contenedor de estado antes del cambio de mensaje y evita anunciar cada pulsación de tecla. Conecta los errores específicos de un campo al campo correspondiente y mantén el estado visual comprensible sin depender únicamente del color.

6. Probar la máquina de estados y observar la política

Prueba la política de ordenamiento seleccionada en lugar de fingir que todas las políticas permiten la misma traza. Para la ruta serializada, verifica que B sea visible pero no se despache hasta que A se resuelva; luego cubre el éxito o fallo de A seguido del éxito o fallo de B, la pérdida de respuesta seguida de la reconciliación y una transformación del servidor. Para cualquier ruta paralela admitida, utiliza un transporte controlable y un stub de servidor para forzar órdenes de procesamiento y respuesta de A-luego-B y B-luego-A. Verifica la proyección renderizada, las operaciones en cola, la versión confirmada y el valor final del servidor después de cada evento.

Luego inyecta un refetch más nuevo y uno más antiguo mientras una mutación está pendiente, un conflicto de colaboradores, recuperación de fuera de línea a en línea, desmontaje y remontaje de componentes, envíos repetidos y cancelación después de que el servidor confirme. Verifica el foco del teclado, los anuncios de estado, las etiquetas de reintento y que un error pertenezca a la tarea y operación correctas.

La telemetría de producción debe separar la latencia percibida de la corrección: latencia de renderizado optimista, latencia de confirmación, tasa de fallos y rollback, tasa de conflictos, tiempo de espera en cola, recuento de operaciones fusionadas, recuento de reintentos y desajuste de reconciliación. Una interfaz rápida que a menudo se corrige a sí misma con un valor sorprendente ha fallado al contrato del producto.

Respuesta de muestra de alta calidad

“Comenzaría preguntando si cada título enviado debe guardarse o si solo importa la última intención del usuario. Aquí importa el último título, la API devuelve versiones de recursos y no necesito escrituras paralelas para una sola tarea. Por lo tanto, permitiría la edición continua pero serializaría los guardados de red por ID de tarea. Si A está en curso y el usuario envía B, la pantalla muestra B de inmediato y B se convierte en la intención en cola fusionada.

El estado para esa tarea tiene una base confirmada, una operación activa y como máximo una intención de título en cola. Cada operación tiene un ID de cliente y la versión base que enviará. El título renderizado es el valor en cola, de lo contrario el valor optimista activo, de lo contrario el título confirmado. Cuando A tiene éxito, adopto la tarea canónica y la versión de su respuesta. No renderizo A porque B todavía está pendiente; envío B contra la nueva versión. Cuando A falla, elimino solo A y sigo ofreciendo guardar B si el fallo es transitorio. Un fallo de validación o de permiso permanece adjunto a la intención rechazada.

Si el servidor informa que otro usuario avanzó la versión, detengo el envío automático, obtengo o adopto el título actual y muestro los valores del servidor y los propuestos para su resolución. No confiaría en ignorar una respuesta antigua, porque eso no puede evitar que una solicitud antigua escriba en último lugar en el servidor. Tampoco trataría el abort como un rollback.

Cada tarea expone su propio estado de guardando, en cola, fallido o en conflicto. El foco permanece en el editor y una región de estado educada preexistente anuncia los resultados del envío sin anunciar cada carácter. Las pruebas controlan la resolución de promesas y el procesamiento del servidor de forma independiente, por lo que puedo demostrar la UI final y el valor del servidor para éxitos reordenados, fallos mixtos, refetches obsoletos, conflictos, respuestas perdidas, reintentos, recuperación sin conexión y desmontajes.”

Errores comunes

  • Guardar una sola instantánea previa a la mutación y restaurarla ante cualquier error → un fallo tardío borra el trabajo optimista más reciente → eliminar la operación fallida y recalcular a partir de la base confirmada más las intenciones restantes.
  • Mantener solo el ID de respuesta más reciente → la UI puede parecer correcta mientras el servidor confirma una solicitud más antigua en último lugar → serializar escrituras dependientes del orden o requerir semánticas de versión o secuencia impuestas por el servidor.
  • Usar un único indicador de carga global → los controles no relacionados se congelan y un fallo no se puede vincular a su recurso → rastrear el estado pendiente y de error por recurso e ID de operación.
  • Tratar cada mutación como repetible (replayable) → los toggles, deltas, eliminaciones y comandos irreversibles tienen diferentes semánticas de fallo → definir una intención explícita y una regla de combinación antes de habilitar el comportamiento optimista.
  • Asumir que la cancelación deshace la solicitud → el servidor puede confirmar antes de observar la cancelación → reconciliar el estado autoritativo y diseñar reintentos para resultados desconocidos.
  • Permitir que cualquier fetch sobrescriba la caché → un refetch obsoleto o una respuesta tardía mueve el estado confirmado hacia atrás → comparar versiones de recursos y reaplicar intenciones pendientes válidas sobre la base más nueva.
  • Ocultar todo el estado pendiente para que la UI se sienta instantánea → los usuarios no pueden explicarse una corrección posterior ni reintentar la acción afectada → mostrar estados locales de guardando, fallo y conflicto mientras se preserva el valor optimista.
  • Probar solo el éxito en el orden de envío → las condiciones de carrera más difíciles quedan sin ejercitar → controlar el orden de respuesta, el orden del servidor, los fallos, los refetches, los reintentos y los remontajes en la matriz de pruebas.

Preguntas de seguimiento y respuestas

¿Qué cambia si el usuario puede editar sin conexión durante varias horas?

Las operaciones pendientes deben ser duraderas, estar limitadas a la cuenta autenticada y al recurso, y repetirse solo después de que el cliente actualice la autorización y las versiones autoritativas. Almacena la semántica de la intención y los ID de operación estables, no instantáneas de UI capturadas. Al reconectar, obtén primero la base, descarta las operaciones que el usuario canceló explícitamente y repite solo las operaciones con una regla de combinación válida. Los permisos caducados, los recursos eliminados y los cambios de esquema requieren un estado bloqueado visible. Para una colaboración prolongada sin conexión, una cola optimista simple puede ser insuficiente; el producto puede necesitar operaciones de combinación específicas del dominio o un protocolo de edición colaborativa.

¿Pueden todas las mutaciones ejecutarse en paralelo si el servidor devuelve versiones?

No. Una versión le dice al cliente qué estado representa una respuesta, pero no define automáticamente cómo se deben ordenar o combinar dos escrituras. El paralelismo es seguro cuando el servidor verifica atómicamente la versión base enviada, impone una secuencia o expone operaciones independientes o conmutativas. De lo contrario, dos escrituras absolutas aún pueden llegar y confirmarse en el orden incorrecto. La serialización es a menudo el contrato más claro para un recurso dependiente del orden.

¿Cómo manejarías la creación optimista cuando el servidor asigna el ID real?

Crea un ID de cliente estable antes de renderizar y úsalo como la identidad de la operación y la clave temporal de la lista. En caso de éxito, registra el mapeo al ID del servidor y reemplaza la entidad confirmada sin remontar filas no relacionadas. Desduplica un reintento mediante la identidad de la operación si el servidor lo admite. Si la creación falla, elimina o marca solo esa entidad optimista. Las operaciones posteriores dirigidas a la entidad temporal deben esperar el mapeo o expresarse en una cola que pueda reescribir el objetivo después de la confirmación.

¿Cuándo elegirías deliberadamente una UI pesimista?

Elígela cuando mostrar el éxito induciría a error al usuario de manera material, la recuperación no sea local, los conflictos sean comunes o la acción sea irreversible o de alto riesgo. Un pago, el otorgamiento de permisos, un envío legal o una acción destructiva masiva pueden acusar recibo del clic de inmediato mostrando un estado pendiente real, y luego mostrar el éxito solo después de la confirmación autoritativa. La interfaz aún puede sentirse receptiva sin atribuirse un resultado que no ha sucedido.

Fuentes públicas

Preguntas relacionadas