Dataslope logoDataslope

Pure Functions

The smallest, most important idea in functional programming — and why it's the foundation of everything that follows

If you remember only one idea from this entire course, make it this one: a pure function is a function whose output depends only on its inputs, and which has no observable side effects.

That's it. Pure functions are the atom of functional programming. LINQ, immutability, composition, parallelism, testing, caching — they all get easier when the building blocks are pure.

A definition you can hold in your head

A function f is pure if and only if:

  1. Same input → same output. Given the same arguments, f always returns the same value. Forever. Across machines, threads, and time of day.
  2. No side effects. f does not write to disk, print, mutate shared state, mutate its arguments, throw on the clock, send a network request, increment a global counter, read the time, or read from a random number generator.

The dashed arrows are what makes g impure: it reaches outside the box.

Pure vs impure: side by side

Code Block
C# 13

Add and Doubled are pure. NowMs, Greet, and AppendOne are not — and each is impure in a different way.

The four superpowers of pure functions

Pure functions are not just an aesthetic preference. They have concrete, measurable advantages.

1. They're trivially testable

A pure function's test is just Assert.Equal(expected, f(input)). There's no setup, no mocking, no teardown.

Code Block
C# 13

Contrast with testing an impure method that writes to a database: you have to spin up a fixture, manage the connection, clean up between tests, and reason about ordering. Pure tests are free.

2. They're safe to run in parallel

Two threads calling a pure function with the same input cannot interfere — there's nothing shared to interfere with. This is why PLINQ (parallel LINQ) works: the operators are assumed to be pure.

3. They're cacheable (memoizable)

f(x) always returns the same value, so you can compute it once and remember it. This is called memoization.

Code Block
C# 13

You can only do this for pure functions. Memoizing an impure function changes its behavior.

4. They're easy to reason about

You can read a pure function top to bottom, and once you understand it, you understand it forever. It doesn't matter who calls it, when, in what order, or with what state. The function is a self-contained mathematical mapping.

Identifying impurity in real code

Most "impurity" in everyday code falls into a small number of categories. Learn to spot them:

PatternWhy it's impure
Console.WriteLine(...)Writes to stdout
File.ReadAllText(...)Reads from disk
DateTime.Now / DateTime.UtcNowHidden input (the clock)
Random.Next()Hidden input (RNG state)
httpClient.GetAsync(...)Network I/O
list.Add(x) (on a parameter)Mutates input
static int counter; counter++;Mutates global
throw new Exception(...) on some inputsSubtle: makes "same input → same output" false

The last one is interesting. A function that throws for some inputs is technically impure (its output is not a return value for those inputs), but in practice we tolerate it for argument validation. The ones above it are the ones that bite.

"But my program has to print things"

Of course. A program that does nothing but compute pure values is useless. Real programs read input, do I/O, and produce output. The functional answer is not "no side effects ever". It is:

Push side effects to the edges of the program. Keep the core pure.

This is sometimes called the functional core, imperative shell pattern.

In a LINQ pipeline, the operators (Where, Select, etc.) are pure. The materialization at the end (ToList, foreach, write to file) is the impure shell.

A multi-file challenge

You'll implement two small pure functions and a small impure "driver" that uses them. The functions are pure; the driver does I/O. This is the functional-core / imperative-shell pattern in miniature.

Challenge
C# 13
Pure core, impure shell

Implement MathCore.SumOfSquares(IEnumerable<int> xs) and MathCore.MeanOfSquares(IEnumerable<int> xs) as pure functions in MathCore.cs.

  • SumOfSquares returns the sum of the squares of its inputs.
  • MeanOfSquares returns the average of the squares of its inputs, or 0.0 for an empty input.

Do not print, mutate the input, or read external state. The Program.cs shell calls them and prints the results.

QuestionSelect one

Which of these C# methods is pure?

static int RollDie() => new Random().Next(1, 7);

static int Triple(int x) => x * 3;

static int LogAndReturn(int x) { System.Console.WriteLine(x); return x; }

static void Add(System.Collections.Generic.List<int> xs, int x) => xs.Add(x);

On this page