Dataslope logoDataslope

The Rise of Declarative Programming

How programmers learned to describe results instead of steps, and where that idea came from

If imperative code says "do this, then do this, then mutate that", declarative code says "this is what I want". The difference looks small on a four-line example and is enormous on a forty-thousand-line codebase.

This page is the prehistory of LINQ — the languages, ideas, and research papers that made it possible to write people.Where(p => p.City == "Berlin").Average(p => p.Age) in a mainstream object-oriented language and have it feel natural.

What "declarative" means

A program is declarative when it describes what result you want, leaving the how to the runtime, library, or compiler.

Two examples you have probably already used:

  • SQL: SELECT city, AVG(age) FROM people WHERE country = 'DE' GROUP BY city — you do not write the loop that scans the table or the hash map that does the grouping. The database does.
  • HTML and CSS: <button class="primary">Save</button> — you do not write the pixel-by-pixel rendering routine.

In both cases you describe the shape of the answer. The system figures out the steps.

Where the idea came from

Declarative programming is not a 2007 invention. By the time C# 3.0 shipped, the idea had been brewing for half a century.

Each step contributed something:

  • LISP showed that you can treat functions as ordinary values you pass around. That is the engine behind every .Where(predicate) you will ever write.
  • SQL showed that ordinary programmers — not just researchers — will happily write declarative queries when the syntax is friendly.
  • Haskell showed that an entire program can be built out of composed pure functions, with laziness as a first-class concept.
  • Smalltalk and Ruby popularized the idea of collection methods like select, collect, and inject that take blocks of code as arguments.

A small declarative shift, in C#

Compare four ways of saying "the names of even numbers, doubled, as a comma-separated string". We will execute each.

Code Block
C# 13

All four print 2, 4, 8, 12. They are operationally equivalent. They are not cognitively equivalent. Each version, top to bottom, moves further from "I am driving a CPU" toward "I am describing a result".

Declarative ≠ slow, declarative ≠ magic

Two myths are worth dispelling now, because they will come up:

  • "Declarative code is slower." In modern .NET, LINQ over arrays and lists is competitive with hand-written loops in the vast majority of code paths. Where it isn't, the difference is usually tiny next to I/O. We will revisit performance in Deferred Execution.
  • "Declarative is just sugar." LINQ is also sugar — but it is built on a real abstraction, IEnumerable<T>, which we will dissect in IEnumerable and Iteration. The "magic" is just a single interface and an extension method.

A larger payoff: rearranging an algorithm

The hidden superpower of declarative code is that the pieces are named, independent, and reorderable. Suppose we want to add "skip the first 1" to our pipeline.

In the imperative version, you have to find the right place to insert a counter, increment it, and add a guard. In the LINQ version, you splice in one operator:

Code Block
C# 13

This is what people mean when they say "LINQ is composable". The pipeline is a sequence of named steps you can reorder, add to, or remove without rewriting the rest.

The rest of the story

The next page is about LINQ specifically: how Microsoft Research designed it, what IEnumerable<T> and lambda expressions had to be in place first, and why this style of querying is now standard across many modern languages.

QuestionSelect one

What is the defining characteristic of declarative code, as discussed on this page?

It uses fewer lines of code than imperative code.

It runs faster than imperative code.

It describes what result is wanted and leaves how to compute it to the runtime or library.

It must be written in a functional programming language.

On this page