Back to course

CS101

Week 1: Functional Mindset & Immutability

Week 1: Functional Mindset & Immutability

What You’ll Learn

  • Why functional programming exists
  • Problems with imperative and OOP approaches
  • What immutability really means
  • Difference between variables and bindings
  • How data flows in functional programs

Concept

Most developers start with an imperative mindset:

  • You create variables
  • You change them over time
  • You rely on loops and mutable state

Example (imperative thinking):

balance = 100

function withdraw(amount) {
  balance = balance - amount
}

Here, balance is constantly changing.

In functional programming:

  • Data does not change
  • Functions do not mutate state
  • Instead, they return new values

Example (functional thinking in Elixir):

balance = 100

withdraw = fn balance, amount ->
  balance - amount
end

new_balance = withdraw.(balance, 20)

Here:

  • balance is never modified
  • new_balance is a new value

Variables vs Bindings

In Elixir, variables are not “boxes that change”.

They are bindings to values.

x = 5
x = 6

This is NOT mutation.

It means:

  • First, x is bound to 5
  • Then, a new binding of x to 6 is created

Why This Matters in Real Systems

In real-world systems, especially concurrent ones:

  • Multiple processes run at the same time
  • Shared mutable state leads to:
    • race conditions
    • unpredictable bugs
    • difficult debugging

Immutability solves this by:

  • removing shared writable state
  • making data predictable
  • allowing safe concurrency

This is one of the core ideas behind the BEAM (Erlang VM):

Processes don’t share memory — they communicate via messages.

Examples

Example 1: Transforming Data

double = fn x -> x * 2 end

Enum.map([1, 2, 3], double)
# [2, 4, 6]

Example 2: No Mutation

list = [1, 2, 3]

new_list = [0 | list]

# list = [1, 2, 3]
# new_list = [0, 1, 2, 3]

Original data remains unchanged.

Example 3: Function Composition

[1, 2, 3]
|> Enum.map(fn x -> x * 2 end)
|> Enum.filter(fn x -> x > 2 end)

Problems

Q1

Rewrite this in functional style:

count = 0

for (i = 0; i < list.length; i++) {
  if (list[i] > 0) {
    count++
  }
}

Q2

What will this return?

x = 10
y = x
x = 20

y

Q3

Convert this to a pure function:

total = 0

function add(x) {
  total += x
}

Q4

Given a list [1, 2, 3, 4], return a new list with all elements doubled.

Q5

Explain why this is dangerous in concurrent systems:

let counter = 0

function increment() {
  counter++
}

Solutions

Show Solutions

Q1

Enum.count(list, fn x -> x > 0 end)

Q2

10

Because y was bound to 10 before x was rebound.

Q3

add = fn total, x ->
  total + x
end

Q4

Enum.map([1, 2, 3, 4], fn x -> x * 2 end)

Q5

Because:

  • multiple threads/processes may modify counter at the same time
  • leads to race conditions
  • results become unpredictable

Summary

  • Functional programming avoids mutable state
  • Variables are bindings, not containers
  • Functions transform data instead of changing it
  • This model scales better for concurrent systems