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
More in Types of Programming