Formal languages for specifying computations that a machine can carry out. They exist because instructing a computer in the numeric codes it actually executes is impractical for anything beyond trivial programs.

A processor executes machine code, numeric instructions specifying operations on particular memory locations and registers. It is unreadable in practice, specific to one processor design, and extremely laborious.

Assembly language substitutes mnemonics for the numbers, so an instruction reads as a short word rather than a value. This is a direct one-to-one translation and remains tied to the particular processor.

An early data processing installation. The first programs were written directly in machine or assembly code, and the labour involved is what motivated higher-level languages.
An early data processing installation. The first programs were written directly in machine or assembly code, and the labour involved is what motivated higher-level languages.Credit: NASA (Public domain).

High-level languages let a programmer express what should happen in terms closer to the problem than to the hardware, and a compiler or interpreter translates it. The gain is threefold: programs are shorter, they are readable by other people, and they can run on any machine with a suitable translator.

A compiler translates the whole program into machine code in advance, producing a file that runs directly. Compiled programs are typically fast and must be compiled separately for each target platform. C and Rust work this way.

An interpreter reads and executes the program as it goes, without producing a separate executable. This makes development quicker and execution slower. Python is usually run this way.

Many modern languages sit between. Java and C sharp compile to an intermediate bytecode which a virtual machine then executes, often compiling frequently used sections to machine code as the program runs.

A short program in C. Even the smallest program shows the language's structure: a function, a call, and a return value.
A short program in C. Even the smallest program shows the language's structure: a function, a call, and a return value.Credit: Esquivalience (CC0).

Imperative programming describes a sequence of steps that change the program's state, which is how the underlying hardware works. Fortran, C and most early languages are imperative.

Object-oriented programming groups data with the operations that act on it, into objects. It was intended to make large programs manageable by localising the effect of changes. Simula introduced the ideas, Smalltalk developed them, and C plus plus, Java and C sharp brought them into mainstream use.

Functional programming treats computation as the evaluation of functions and avoids changing state. Its advantage is that a function whose output depends only on its input is easier to reason about and to run in parallel. Lisp is the ancestor; Haskell is the strict modern example, and functional features have been absorbed into most mainstream languages.

Declarative languages specify what is wanted rather than how to obtain it. SQL is the most widely used example: a query describes the desired result and the database decides how to retrieve it.

Most languages in practical use today combine several of these.

Fortran, released in 1957, was the first widely used high-level language, designed for scientific computation, and it demonstrated that compiled code could be efficient enough to replace hand-written assembly.

COBOL, from 1959, was designed for business data processing with deliberately English-like syntax, and Grace Hopper's work on compilers and on readable languages was central to it. Very large quantities of COBOL remain in production in banking and government.

Lisp, from 1958, introduced ideas including recursion, garbage collection and treating code as data, many of which took decades to reach mainstream languages.

C, from 1972, gave low-level control with high-level structure and became the language in which operating systems are written. Its influence on syntax is visible in most languages designed since.

Python, from 1991, prioritised readability and became dominant in data analysis, scientific computing and machine learning.

JavaScript, created in 1995 for web browsers, became unavoidable because it is the only language browsers execute natively.

A parsed program structure. A compiler first analyses the text into a tree representing its structure, which is the step that makes translation possible.
A parsed program structure. A compiler first analyses the text into a tree representing its structure, which is the step that makes translation possible.Credit: Lulu of the Lotus-Eaters at English Wikipedia (Public domain).

Type systems determine what kinds of value a variable may hold. Static typing checks this before the program runs, catching a class of error early at the cost of more verbose code. Dynamic typing checks during execution, which is more flexible and defers errors to runtime.

Memory management is the other major axis. C requires the programmer to allocate and release memory manually, which is fast and is the source of a large share of security vulnerabilities. Garbage-collected languages reclaim memory automatically at some cost in performance and predictability. Rust enforces memory safety at compile time through ownership rules, which is a third approach and is why it has been adopted for systems programming where both safety and speed are required.

Programming languages determine what software is practical to build, which errors are possible, and how many people can contribute to a project. The persistent shift toward languages that catch more mistakes automatically reflects the fact that programmer time and program correctness have become more expensive than machine time.

They also demonstrate an unusual property of the field: the abstractions are stacked, so a program in a high-level language is translated into a lower-level one and eventually into signals in hardware, and almost nobody working at one level needs to understand the levels below.