CodeGym /Courses /C# SELF /Pure functions and immutability

Pure functions and immutability

C# SELF
Level 51, Lesson 1
Available

1. Introduction

Pure function — a function that given the same inputs always returns the same result and causes no side effects. Put simply: a pure function doesn't "mess up" anything around it and isn't influenced by external state.

A function is considered pure if:

  • It only computes a value based on its input arguments.
  • It doesn't modify external variables or program state.
  • It doesn't depend on external variables or states that can change.

Two golden rules of purity

  1. Determinism: the same input — the same output.
  2. No side effects: the function doesn't change anything outside itself: no files, no globals, no UI (looking at you, Console.WriteLine).

Not a religion, just common sense

It may look like an academic thing, but practical benefits are clear:

  • A pure function is predictable. It's easy to test — you call it with specific args and get a specific result.
  • You can freely reorder pure functions in code without fearing hidden breakages.
  • In a multicore world a pure function is safe: you can run it in parallel without data race worries.

2. Examples of pure and impure functions

Pure function: everything is predictable

// Pure function: doesn't touch anything outside itself
int Add(int a, int b)
{
    return a + b;
}

int Square(int x)
{
    return x * x;
}

Call Add(2, 3) a hundred times — you'll always get 5. Boring, but reliable.

Impure function: breaking the rules

// Breaks purity: depends on external state (static variable)
int counter = 0;

int Increase()
{
    counter++;
    return counter;
}

Here calling Increase() returns a different value each time — so no determinism.

// Breaks purity: produces an external effect (prints to screen)
int AddAndPrint(int a, int b)
{
    int sum = a + b;
    Console.WriteLine(sum); // Side effect!
    return sum;
}

What about randomness and time?

Any function using DateTime.Now or Random is already impure:

// Not pure!
int GetRandomNumber()
{
    return new Random().Next();
}

Difference table

Characteristic Pure function Impure function
Always same result for same args Yes No
Side effects No Yes
Depends on external state No Yes

3. Data immutability: theory and practice

Immutability — an approach where an object cannot be changed after creation. If you need a new value — you create a new object.

Why it matters?

  • The app becomes resilient to accidental data mutations.
  • There are no secret "leaks" of changes: if you hold an object, nobody can quietly change its field.
  • Immutability is the basis for many automatic optimizations and parallel computations.

Simple examples in C#

Immutable types in .NET

Strings (string) in C# are immutable! Every time you do string.Concat(s, "world") a new string is created.

string s = "Hello";
string t = s;
s = s + " World";
Console.WriteLine(t); // t == "Hello"

Arrays and collections: mutable by default

int[] numbers = { 1, 2, 3 };
numbers[0] = 42; // The array changed!

Immutability in simple terms: compiling code

Instead of mutating an existing object/value, return a new one:

// Instead of this:
void AddToList(List<int> list, int value)
{
    list.Add(value); // Mutates!
}

// Better:
List<int> AddToList(List<int> list, int value)
{
    var newList = new List<int>(list) { value }; // New list
    return newList;
}

Illustration: mutate vs not mutate

flowchart LR
    A[Original object] --"mutation"--> B[Same object, but different inside]
    A --"immutability"--> C[New object]

4. Why this matters in real C# code?

  • Modern C# libraries like LINQ, Entity Framework and ASP.NET Core favor pure functions and immutability.
  • Immutability reduces "magical" bugs where someone somewhere overwrote an important value.
  • Pure functions make unit testing easy, because a test just checks input→output without worrying about the outside world.

Example: working with strings

string s = "Hello";
string newS = s.Replace("H", "J"); // s is still "Hello"; newS is "Jello"

Example: LINQ and collections

Where, Select and other methods return new collections, not touching the old ones.

var numbers = new List<int> { 1, 2, 3, 4 };
var evenNumbers = numbers.Where(n => n % 2 == 0).ToList();

// numbers stayed the same!

Real-world example: configuration with immutable objects

Many modern .NET APIs use immutable objects for configuration, for example JsonSerializerOptions:

var options = new JsonSerializerOptions
{
    WriteIndented = true
};
// This object isn't changed "in the middle of the play", which increases reliability.

5. Common mistakes and pitfalls

The slippery slope starts when you "accidentally" mutate data in supposedly "pure" code.

It often happens with collections: you intended to filter a list, but ended up changing the original.

Or you forgot that a method like List<T>.Add mutates the object in place.

Tricky example:

List<int> DoubleTheNumbers(List<int> xs)
{
    // Bug! We mutate the original list and return the same object.
    foreach (var i in xs)
        xs.Add(i * 2);
    return xs;
}

This code will even throw a runtime error (InvalidOperationException) because we're changing the collection while iterating — a classic mutation problem.

Correct:

List<int> DoubleTheNumbers(List<int> xs)
{
    var newList = new List<int>(xs.Select(x => x * 2));
    return newList;
}
2
Task
C# SELF, level 51, lesson 1
Locked
Immutable List of Numbers
Immutable List of Numbers
Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION