Types of Programming

Procedural Programming

Structuring code as a sequence of instructions grouped into reusable procedures.

Overview

Procedural Programming organises code as a sequence of procedures (subroutines, functions) that operate on data passed to them. It is the direct descendant of imperative programming with the addition of named reusable subroutines. C, Pascal, BASIC, and early FORTRAN are archetypal procedural languages. It is the foundation most programmers learn first.

Origin

FORTRAN (1957) and COBOL (1959) introduced named subroutines. Edsger Dijkstra's "Go To Statement Considered Harmful" (1968) pushed structured programming: loops and conditionals instead of GOTO. Pascal (Niklaus Wirth, 1970) embodied structured procedural programming. C (Dennis Ritchie, 1972) became the defining procedural systems language.

Examples

Procedural data processing in C-style JavaScript

interface Order {
  id: number;
  items: { price: number; qty: number }[];
  discountCode: string | null;
}

function calcSubtotal(items: Order['items']): number {
  let total = 0;
  for (const item of items) {
    total += item.price * item.qty;
  }
  return total;
}

function applyDiscount(subtotal: number, code: string | null): number {
  if (code === 'SAVE10') return subtotal * 0.9;
  if (code === 'SAVE20') return subtotal * 0.8;
  return subtotal;
}

function calcTax(amount: number, rate: number): number {
  return amount * rate;
}

function processOrder(order: Order): number {
  const subtotal = calcSubtotal(order.items);
  const discounted = applyDiscount(subtotal, order.discountCode);
  const tax = calcTax(discounted, 0.08);
  return discounted + tax;
}

Each function does one thing, takes explicit arguments, and returns a value. The control flow is obvious and the procedures are independently testable. This is the model that structured programming advocates (Dijkstra, Wirth) promoted.

Procedural file processing in Ruby

require 'csv'
require 'json'

def read_csv(path)
  CSV.read(path, headers: true).map(&:to_h)
end

def filter_active(records)
  records.select { |r| r['status'] == 'active' }
end

def transform_record(record)
  {
    id: record['id'].to_i,
    name: record['name'].strip,
    email: record['email'].downcase,
    joined_at: Date.parse(record['created_at'])
  }
end

def write_json(data, path)
  File.write(path, JSON.pretty_generate(data))
end

def run(input_path, output_path)
  raw = read_csv(input_path)
  active = filter_active(raw)
  transformed = active.map { |r| transform_record(r) }
  write_json(transformed, output_path)
  puts "Wrote #{transformed.size} records to #{output_path}"
end

run(ARGV[0], ARGV[1])

Each procedure has a clear interface. run orchestrates them. No classes are needed for a script this size. Adding a validation step means inserting one function call in run; nothing else changes.

Use Cases

  • 01Shell scripts and CLI tools where a linear procedure with clear steps is the natural model
  • 02Systems programming in C where direct hardware access and minimal abstraction overhead are required
  • 03Data migration scripts where a sequence of read/transform/write operations has no meaningful object model
  • 04Scientific computing scripts where researchers want to read code as a straight sequence of mathematical operations

When Not to Use

  • //Large codebases with complex domain models where procedural code leads to global state or functions with too many parameters
  • //When multiple representations of the same entity (a User as HTTP response, database record, and domain object) need clean separation
  • //GUI applications where event-driven or reactive models map more naturally to user interaction than top-down procedure calls

Technical Notes

  • C's lack of namespaces means procedural codebases use naming prefixes (gtk_, SDL_) to avoid symbol collisions; this is a procedural-scale limitation that OOP namespaces solve
  • Structured programming's three control structures (sequence, selection, iteration) are provably sufficient to express any computable function (Bohm-Jacopini theorem, 1966)
  • Go is a modern procedural language by design: Russ Cox and Rob Pike deliberately excluded inheritance to keep the language's control flow easy to follow
  • Pascal introduced the procedure/function distinction (procedures have side effects; functions return values) that influenced Ada and later structured the "command/query separation" principle