DevOps 11 min read

Gleam Programming Language: The Complete Beginner's Guide (2025)

Learn Gleam, the type-safe functional language running on Erlang's BEAM. Installation, syntax, pattern matching, and building your first project with examples.

MR

Moshiour Rahman

Advertisement

A new language is quietly winning over developers. In Stack Overflow’s 2025 survey, Gleam ranked as the 2nd most admired programming language with 70% of developers wanting to continue using it.

What makes Gleam special? It brings static typing to the battle-tested Erlang ecosystem—the same platform that powers WhatsApp’s 2 billion users.

This guide takes you from zero to productive in Gleam.

What is Gleam?

Gleam is a statically typed functional programming language that runs on the Erlang virtual machine (BEAM). It also compiles to JavaScript for browser environments.

Think of it as: TypeScript’s type safety + Erlang’s scalability + Rust’s friendly errors

Why Gleam Matters

FeatureWhat It Means
Type-safeCatch bugs at compile time, not production
BEAM runtimeSame VM that powers WhatsApp, Discord, Ericsson
Compiles to JSRun the same code in browsers
No null/exceptionsExplicit error handling, no surprises
Immutable dataPredictable state, easy concurrency
Friendly errorsClear messages that help you fix issues

Gleam vs Other Languages

AspectGleamElixirRustTypeScript
Type systemStaticDynamicStaticStatic
RuntimeBEAMBEAMNativeNode/Browser
Null safetyNo nullsHas nilOption typeOptional
Learning curveGentleGentleSteepModerate
ConcurrencyActor modelActor modelThreadsAsync/await
Error handlingResult typeExceptionsResult typeExceptions

Installation

Gleam provides a single binary containing everything: compiler, build tool, formatter, and package manager.

macOS

brew install gleam

Linux

# Using asdf (recommended)
asdf plugin add gleam
asdf install gleam latest
asdf global gleam latest

# Or download directly
curl -fsSL https://gleam.run/install.sh | sh

Windows

# Using scoop
scoop install gleam

# Or download from GitHub releases
# https://github.com/gleam-lang/gleam/releases

Verify Installation

gleam --version
# gleam 1.6.0

Your First Gleam Project

Let’s create a project:

gleam new hello_gleam
cd hello_gleam

This creates:

hello_gleam/
├── gleam.toml        # Project configuration
├── src/
│   └── hello_gleam.gleam  # Main source file
└── test/
    └── hello_gleam_test.gleam

The Main File

Open src/hello_gleam.gleam:

import gleam/io

pub fn main() {
  io.println("Hello from Gleam!")
}

Run it:

gleam run
# Hello from Gleam!

Gleam Syntax Fundamentals

Variables (Let Bindings)

Variables in Gleam are immutable by default:

let name = "TechyOwls"
let age = 25
let pi = 3.14159

// This won't compile - variables are immutable
// name = "Something else"  // Error!

Type Annotations

Gleam infers types, but you can be explicit:

let name: String = "TechyOwls"
let count: Int = 42
let price: Float = 19.99
let active: Bool = True

Functions

Functions are defined with fn:

// Basic function
fn greet(name: String) -> String {
  "Hello, " <> name <> "!"
}

// Public function (exported)
pub fn add(a: Int, b: Int) -> Int {
  a + b
}

// Function with multiple expressions
pub fn calculate_total(price: Float, quantity: Int) -> Float {
  let subtotal = price *. int.to_float(quantity)
  let tax = subtotal *. 0.1
  subtotal +. tax
}

Note: Gleam uses + for integers, +. for floats (no implicit conversion).

Anonymous Functions

let double = fn(x: Int) -> Int { x * 2 }

let result = double(5)  // 10

Pipe Operator

The pipe operator |> chains function calls:

import gleam/string
import gleam/io

pub fn main() {
  "hello world"
  |> string.uppercase
  |> string.reverse
  |> io.println
  // Prints: DLROW OLLEH
}

This is equivalent to:

io.println(string.reverse(string.uppercase("hello world")))

Data Types

Lists

Lists hold multiple values of the same type:

let numbers = [1, 2, 3, 4, 5]
let names = ["Alice", "Bob", "Charlie"]

// Prepend to list
let more_numbers = [0, ..numbers]  // [0, 1, 2, 3, 4, 5]

Tuples

Fixed-size collections of different types:

let person = #("Alice", 30, True)

// Access by position
let name = person.0   // "Alice"
let age = person.1    // 30

Custom Types (Records)

Define your own types:

// Define a type
pub type User {
  User(name: String, email: String, age: Int)
}

// Create an instance
let user = User(name: "Alice", email: "alice@example.com", age: 28)

// Access fields
let name = user.name  // "Alice"

Variants (Sum Types)

Types with multiple variants:

pub type Status {
  Active
  Inactive
  Pending(reason: String)
}

pub type Result(value, error) {
  Ok(value)
  Error(error)
}

Pattern Matching

Pattern matching is Gleam’s superpower:

Basic Matching

pub fn describe_number(n: Int) -> String {
  case n {
    0 -> "zero"
    1 -> "one"
    2 -> "two"
    _ -> "many"
  }
}

Matching on Types

pub type Shape {
  Circle(radius: Float)
  Rectangle(width: Float, height: Float)
  Square(side: Float)
}

pub fn area(shape: Shape) -> Float {
  case shape {
    Circle(r) -> 3.14159 *. r *. r
    Rectangle(w, h) -> w *. h
    Square(s) -> s *. s
  }
}

Matching Lists

pub fn first_element(list: List(a)) -> Result(a, String) {
  case list {
    [] -> Error("Empty list")
    [first, ..] -> Ok(first)
  }
}

pub fn sum(numbers: List(Int)) -> Int {
  case numbers {
    [] -> 0
    [first, ..rest] -> first + sum(rest)
  }
}

Guards

Add conditions to patterns:

pub fn classify_age(age: Int) -> String {
  case age {
    a if a < 0 -> "Invalid"
    a if a < 13 -> "Child"
    a if a < 20 -> "Teenager"
    a if a < 65 -> "Adult"
    _ -> "Senior"
  }
}

Error Handling

Gleam has no exceptions. Errors are values:

The Result Type

import gleam/result

pub fn divide(a: Float, b: Float) -> Result(Float, String) {
  case b {
    0.0 -> Error("Cannot divide by zero")
    _ -> Ok(a /. b)
  }
}

pub fn main() {
  let result = divide(10.0, 2.0)

  case result {
    Ok(value) -> io.println("Result: " <> float.to_string(value))
    Error(msg) -> io.println("Error: " <> msg)
  }
}

Chaining Results

import gleam/result

pub fn process_data(input: String) -> Result(Int, String) {
  input
  |> parse_input
  |> result.then(validate)
  |> result.then(transform)
}

The Option Type

For values that might not exist:

import gleam/option.{type Option, None, Some}

pub fn find_user(id: Int) -> Option(User) {
  case id {
    1 -> Some(User("Alice", "alice@example.com", 28))
    _ -> None
  }
}

Working with Lists

import gleam/list
import gleam/io

pub fn main() {
  let numbers = [1, 2, 3, 4, 5]

  // Map - transform each element
  let doubled = list.map(numbers, fn(x) { x * 2 })
  // [2, 4, 6, 8, 10]

  // Filter - keep matching elements
  let evens = list.filter(numbers, fn(x) { x % 2 == 0 })
  // [2, 4]

  // Fold - reduce to single value
  let sum = list.fold(numbers, 0, fn(acc, x) { acc + x })
  // 15

  // Find - get first match
  let found = list.find(numbers, fn(x) { x > 3 })
  // Ok(4)

  // Chained operations
  numbers
  |> list.map(fn(x) { x * 2 })
  |> list.filter(fn(x) { x > 5 })
  |> list.fold(0, fn(acc, x) { acc + x })
  |> int.to_string
  |> io.println
  // "24" (6 + 8 + 10)
}

Modules and Imports

Creating Modules

// src/math_utils.gleam
pub fn square(n: Int) -> Int {
  n * n
}

pub fn cube(n: Int) -> Int {
  n * n * n
}

// Private function (not exported)
fn internal_helper() {
  // ...
}

Importing Modules

// Import entire module
import math_utils

// Use with prefix
let result = math_utils.square(5)

// Import specific functions
import gleam/list.{map, filter}

// Import with alias
import gleam/string as str

Building a Real Project

Let’s build a simple task manager:

// src/task.gleam
import gleam/option.{type Option, None, Some}

pub type Priority {
  Low
  Medium
  High
}

pub type Task {
  Task(
    id: Int,
    title: String,
    completed: Bool,
    priority: Priority,
  )
}

pub fn new(id: Int, title: String) -> Task {
  Task(id: id, title: title, completed: False, priority: Medium)
}

pub fn complete(task: Task) -> Task {
  Task(..task, completed: True)
}

pub fn set_priority(task: Task, priority: Priority) -> Task {
  Task(..task, priority: priority)
}

pub fn is_high_priority(task: Task) -> Bool {
  case task.priority {
    High -> True
    _ -> False
  }
}
// src/task_list.gleam
import gleam/list
import task.{type Task, type Priority, High}

pub type TaskList {
  TaskList(tasks: List(Task), next_id: Int)
}

pub fn new() -> TaskList {
  TaskList(tasks: [], next_id: 1)
}

pub fn add(task_list: TaskList, title: String) -> TaskList {
  let new_task = task.new(task_list.next_id, title)
  TaskList(
    tasks: [new_task, ..task_list.tasks],
    next_id: task_list.next_id + 1,
  )
}

pub fn complete_task(task_list: TaskList, id: Int) -> TaskList {
  let updated_tasks = list.map(task_list.tasks, fn(t) {
    case t.id == id {
      True -> task.complete(t)
      False -> t
    }
  })
  TaskList(..task_list, tasks: updated_tasks)
}

pub fn get_high_priority(task_list: TaskList) -> List(Task) {
  list.filter(task_list.tasks, task.is_high_priority)
}

pub fn pending_count(task_list: TaskList) -> Int {
  task_list.tasks
  |> list.filter(fn(t) { !t.completed })
  |> list.length
}
// src/main.gleam
import gleam/io
import gleam/int
import task_list

pub fn main() {
  let tasks = task_list.new()
    |> task_list.add("Learn Gleam basics")
    |> task_list.add("Build a project")
    |> task_list.add("Deploy to production")

  let pending = task_list.pending_count(tasks)

  io.println("Pending tasks: " <> int.to_string(pending))

  let updated = task_list.complete_task(tasks, 1)
  let new_pending = task_list.pending_count(updated)

  io.println("After completing one: " <> int.to_string(new_pending))
}

Run it:

gleam run
# Pending tasks: 3
# After completing one: 2

Compiling to JavaScript

Gleam compiles to JavaScript for browser or Node.js:

# Set target in gleam.toml
# target = "javascript"

gleam build --target javascript

The output goes to build/dev/javascript/.

Using JavaScript Libraries

// External JavaScript function
@external(javascript, "./ffi.js", "alert")
pub fn show_alert(message: String) -> Nil
// ffi.js
export function alert(message) {
  window.alert(message);
}

Testing

Gleam has built-in testing:

// test/task_test.gleam
import gleeunit
import gleeunit/should
import task

pub fn main() {
  gleeunit.main()
}

pub fn new_task_test() {
  let t = task.new(1, "Test task")

  t.id |> should.equal(1)
  t.title |> should.equal("Test task")
  t.completed |> should.equal(False)
}

pub fn complete_task_test() {
  let t = task.new(1, "Test")
    |> task.complete

  t.completed |> should.equal(True)
}

Run tests:

gleam test

Package Management

Adding Dependencies

Edit gleam.toml:

[dependencies]
gleam_stdlib = ">= 0.34.0 and < 2.0.0"
gleam_json = ">= 1.0.0 and < 2.0.0"

Or use the CLI:

gleam add gleam_json
gleam add gleam_http
PackagePurpose
gleam_jsonJSON encoding/decoding
gleam_httpHTTP client/server
gleam_otpOTP behaviors (GenServer, etc.)
lustreFrontend framework (like Elm)
wispWeb framework
mistHTTP server

Gleam for Web Development

Backend with Wisp

import wisp.{type Request, type Response}
import gleam/http.{Get, Post}

pub fn handle_request(req: Request) -> Response {
  case req.method, wisp.path_segments(req) {
    Get, [] -> home_page()
    Get, ["api", "users"] -> list_users()
    Post, ["api", "users"] -> create_user(req)
    _, _ -> wisp.not_found()
  }
}

fn home_page() -> Response {
  wisp.ok()
  |> wisp.html_body("<h1>Welcome to Gleam!</h1>")
}

Frontend with Lustre

import lustre
import lustre/element.{text}
import lustre/element/html.{button, div, p}
import lustre/event

pub type Model {
  Model(count: Int)
}

pub type Msg {
  Increment
  Decrement
}

pub fn init(_) -> Model {
  Model(count: 0)
}

pub fn update(model: Model, msg: Msg) -> Model {
  case msg {
    Increment -> Model(count: model.count + 1)
    Decrement -> Model(count: model.count - 1)
  }
}

pub fn view(model: Model) -> element.Element(Msg) {
  div([], [
    button([event.on_click(Decrement)], [text("-")]),
    p([], [text(int.to_string(model.count))]),
    button([event.on_click(Increment)], [text("+")]),
  ])
}

pub fn main() {
  lustre.simple(init, update, view)
  |> lustre.start("#app", Nil)
}

Why Learn Gleam in 2025?

ReasonDetails
Growing fast70% approval in Stack Overflow 2025, Thoughtworks Technology Radar
Type safetyCatch errors before they reach production
BEAM reliabilitySame foundation as WhatsApp, Discord
Full-stackOne language for backend and frontend
Great toolingFormatter, LSP, package manager included
Friendly communityWelcoming to beginners

Summary

ConceptGleam Approach
VariablesImmutable with let
Functionsfn keyword, explicit types
ErrorsResult and Option types
DataCustom types and variants
Control flowPattern matching with case
CompositionPipe operator |>
ModulesFile-based, explicit exports

Gleam combines the best of functional programming with practical tooling. If you want type safety on one of the most reliable runtimes in existence, Gleam is worth learning.

# Get started now
gleam new my_project
cd my_project
gleam run

Interested in more functional programming? Check out our guides on Python async programming and TypeScript advanced patterns.

Advertisement

MR

Moshiour Rahman

Software Architect & AI Engineer

Share:
MR

Moshiour Rahman

Software Architect & AI Engineer

Enterprise software architect with deep expertise in financial systems, distributed architecture, and AI-powered applications. Building large-scale systems at Fortune 500 companies. Specializing in LLM orchestration, multi-agent systems, and cloud-native solutions. I share battle-tested patterns from real enterprise projects.

Related Articles

Comments

Comments are powered by GitHub Discussions.

Configure Giscus at giscus.app to enable comments.