Saltar al contenido
Explorar conocimiento

concepto · fundamentos · 11 min de lectura

Programación Imperativa

La programación imperativa describe la computación como una secuencia de instrucciones que modifican el estado del programa, usando variables, asignaciones y estructuras de control para decirle a la máquina exactamente qué hacer y en qué orden.

No requiere conocimientos previos.

Alcance en breve

Cubre

  • imperative programming as the paradigm of explicit step-by-step instructions
  • the three control structures: sequence, selection, and iteration
  • variables, assignment, and state mutation as core mechanisms
  • structured programming and the elimination of GOTO
  • contrast with declarative paradigms (functional, logic, SQL)

No cubre

  • assembly language and machine code details
  • historical computing models (Turing machines, von Neumann architecture) beyond conceptual reference
  • specific language syntax comparisons beyond TypeScript examples

Supone

  • The reader has written basic code in any language and understands variables, if statements, loops, and function calls.

Resumen

La programación imperativa es el paradigma más antiguo y el más cercano a cómo funciona el hardware. Consiste en darle a la computadora una lista ordenada de instrucciones que cambian el estado del programa paso a paso. Cada instrucción dice «hacé esto», y el programa avanza secuencialmente, modificando variables y tomando decisiones con condicionales y bucles.

Importa porque es el paradigma base. Casi todos los lenguajes mainstream —JavaScript, Python, Java, C#, C++, Go, Rust— son multi-paradigma pero tienen un núcleo imperativo. Aunque uses clases y objetos (POO) o funciones puras y composición (FP), por debajo el procesador ejecuta instrucciones una tras otra. Entender el paradigma imperativo es entender el modelo mental con el que arranca cualquier persona que programa.

Su fortaleza es la simplicidad conceptual: el código se lee como una receta, de arriba hacia abajo, y el estado del programa en cada punto es explícito. Su debilidad es que, sin disciplina, el estado compartido y los efectos laterales pueden volver el código difícil de razonar y testear cuando crece.

Alcance y supuestos

Este paquete cubre la programación imperativa como paradigma: el modelo de instrucciones secuenciales, las tres estructuras de control (secuencia, selección e iteración), el rol de la mutación de estado, y la evolución desde GOTO hacia la programación estructurada. También lo contrasta con los paradigmas declarativo, funcional y orientado a objetos, situándolo como el cimiento sobre el que se construyen los demás.

No cubre ensamblador, arquitectura de computadoras, ni los detalles históricos de la crisis del software de los años 60 más allá de una breve referencia. Tampoco cubre técnicas de optimización de bajo nivel como loop unrolling o vectorización.

Asume que ya escribiste código en algún lenguaje y entendés qué son una variable, un if, un for y una función.

Modelo mental

Imaginá que le explicás a alguien cómo hacer un sándwich. No le decís «quiero un sándwich de jamón y queso caliente». Le decís: «Abrí el pan. Poné dos fetas de jamón sobre la base. Poné una feta de queso encima. Cerrá el sándwich. Ponelo en la sandwichera. Esperá tres minutos. Retiralo. Cortalo por la diagonal.»

Esa secuencia es programación imperativa: cada paso es una orden, el orden importa, y el estado del sándwich cambia después de cada instrucción. Si invertís el orden —cortar antes de calentar— el resultado es distinto.

En código, el estado son las variables. Las instrucciones son las líneas que se ejecutan una tras otra. La gracia del paradigma está en que podés mirar cualquier línea del programa y saber exactamente qué valor tiene cada variable en ese punto, siempre que entiendas lo que pasó antes.

Compará eso con SQL, que es declarativo: SELECT name FROM users WHERE age > 18. No le decís al motor cómo recorrer la tabla, qué índice usar o en qué orden comparar. Solo describís el resultado que querés. La programación imperativa es lo opuesto: le decís explícitamente cada paso.

Uso práctico

El paradigma imperativo es la opción natural cuando el problema se describe mejor como una secuencia de pasos con estado compartido:

  • ✅ Scripts de automatización: leer un archivo, transformarlo y escribirlo.
  • ✅ Algoritmos con estado intermedio complejo que cambia en cada iteración.
  • ✅ Código donde el rendimiento importa y necesitás control fino sobre la memoria y el orden de ejecución.
  • ❌ Lógica que se describe mejor como «quiero este resultado» que como «hacé esto, después esto, después esto» —ahí lo declarativo es más claro.
  • ❌ Sistemas con mucho estado compartido entre componentes: sin disciplina adicional (objetos, módulos) el código imperativo puro se vuelve frágil.

Las tres estructuras de control

La programación estructurada —el estándar desde los años 70— redujo toda la lógica de control a tres patrones. Cualquier programa se puede escribir usando solo estos tres. El contraste histórico es con GOTO, que permitía saltar a cualquier línea del programa y producía el famoso «código espagueti», inentendible incluso para quien lo escribió.

Secuencia: las instrucciones se ejecutan en orden, una después de otra.

let total = 0;
total += 100;
total *= 1.21;
console.log(total); // 121

Selección: el programa elige entre caminos alternativos según una condición.

let status: string;
if (total > 200) {
  status = "free shipping";
} else {
  status = "standard shipping";
}

Iteración: el programa repite un bloque mientras se cumpla una condición.

let sum = 0;
for (let i = 1; i <= 100; i++) {
  sum += i;
}
// sum = 5050

Estos tres patrones, combinados con funciones para agrupar bloques de lógica, son la base de todo programa imperativo. La diferencia con GOTO es que las estructuras de control tienen un solo punto de entrada y un solo punto de salida. Eso permite leer el código de arriba hacia abajo sin perseguir saltos.

Mutación de estado: la espada de doble filo

La mutación —cambiar el valor de una variable— es la herramienta central del paradigma imperativo y su mayor riesgo.

let counter = 0;
counter += 1; // mutación explícita
counter += 1; // counter ahora es 2

La mutación es directa y eficiente. Para el procesador, cambiar una posición de memoria es una operación barata. Pero cuando muchas partes del programa mutan las mismas variables, aparecen los bugs difíciles de reproducir: una función cambió algo que otra función no esperaba, en un orden que solo ocurre a veces.

La POO responde a ese problema encapsulando la mutación dentro de objetos. La FP responde eliminando la mutación por completo. La programación imperativa pura no da una respuesta: deja la disciplina en manos de quien programa. Por eso, en la práctica, el código imperativo moderno casi siempre adopta alguna de esas restricciones adicionales.

Contraste con paradigmas declarativos

El espectro imperativo-declarativo es una de las distinciones más útiles para entender lenguajes y estilos:

| Paradigma | Imperativo | Declarativo | |---|---|---| | Pregunta | ¿Cómo? | ¿Qué? | | Control de flujo | Explícito (loops, ifs) | Implícito (motor) | | Estado | Mutable | Inmutable o abstracto | | Ejemplos | C, Python, JS (estilo) | SQL, HTML, CSS, regex | | Paralelismo | Difícil (coordinar mutaciones) | Más fácil (sin efectos laterales) |

Lo declarativo no es mejor que lo imperativo: es mejor para ciertos problemas. Nadie quiere escribir un compilador de SQL en SQL. Nadie quiere escribir validación de formularios en C puro si tiene React. El programador Senior con 14 años de experiencia no elige un paradigma por ideología: elige el que hace el código más claro para el próximo que lo lea.

Ejemplo trabajado: procesar una lista de tareas

Un sistema procesa una lista de tareas pendientes. Cada tarea tiene un nombre y un esfuerzo estimado en horas. El programa debe filtrar las tareas urgentes (esfuerzo menor a 4 horas), ordenarlas por esfuerzo ascendente y calcular el tiempo total.

La versión imperativa describe explícitamente cada paso:

interface Task {
  name: string;
  effort: number;
}

function processTasks(tasks: Task[]) {
  // Paso 1: filtrar urgentes
  const urgent: Task[] = [];
  for (let i = 0; i < tasks.length; i++) {
    if (tasks[i].effort < 4) {
      urgent.push(tasks[i]);
    }
  }

  // Paso 2: ordenar por esfuerzo (burbuja, para que el orden sea explícito)
  for (let i = 0; i < urgent.length - 1; i++) {
    for (let j = 0; j < urgent.length - i - 1; j++) {
      if (urgent[j].effort > urgent[j + 1].effort) {
        const temp = urgent[j];
        urgent[j] = urgent[j + 1];
        urgent[j + 1] = temp;
      }
    }
  }

  // Paso 3: sumar esfuerzo total
  let totalEffort = 0;
  for (let i = 0; i < urgent.length; i++) {
    totalEffort += urgent[i].effort;
  }

  return { urgent, totalEffort };
}

El código muestra con transparencia quirúrgica qué operaciones se ejecutan, en qué orden y sobre qué datos. Si el programa falla en la línea 28, sabés que urgent[i] tenía el valor que le asignaste en el bucle de la línea 9. No hay magia.

La misma lógica en estilo funcional sería más corta pero menos explícita sobre el cómo:

function processTasks(tasks: Task[]) {
  const urgent = tasks
    .filter(t => t.effort < 4)
    .sort((a, b) => a.effort - b.effort);

  const totalEffort = urgent.reduce((sum, t) => sum + t.effort, 0);

  return { urgent, totalEffort };
}

¿Cuál es mejor? Depende del contexto. La versión imperativa te da control total sobre cada iteración y cada comparación; es ideal si necesitás optimizar el ordenamiento para un caso específico o si estás enseñando algoritmos. La versión funcional comunica la intención más rápido: un lector experimentado entiende el pipeline en segundos. En producción, probablemente escribirías la segunda y reservarías la primera para cuando el perfil de rendimiento diga que sort nativo no alcanza.

Antes de decidir, tres preguntas:

  1. ¿El problema se describe naturalmente como una secuencia de pasos donde el orden importa?
  2. ¿Necesitás control fino sobre cómo se ejecuta cada paso (uso de memoria, orden de iteración, early exit)?
  3. ¿El equipo y el dominio se benefician de la transparencia total sobre el estado en cada punto del programa?

Si dos de tres son sí, el estilo imperativo probablemente produzca el código más claro. Si el problema es transformar datos de una forma a otra sin que el orden de las transformaciones individuales importe, el estilo funcional comunica mejor la intención.

Evidencia

  • Wikipedia, Imperative programming traza la historia del paradigma desde los primeros lenguajes hasta los multi-paradigma modernos y explica su relación con la arquitectura von Neumann.
  • Wikipedia, Structured programming documenta el movimiento que reemplazó GOTO con las tres estructuras de control y estableció las bases del diseño de software moderno.
  • MDN Web Docs, Loops and iteration cubre todas las formas de iteración en JavaScript, el mecanismo central de la programación imperativa.
  • MDN Web Docs, Functions explica cómo las funciones agrupan bloques de instrucciones imperativas en unidades reutilizables.

Fuentes citadas