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.
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
| Feature | What It Means |
|---|---|
| Type-safe | Catch bugs at compile time, not production |
| BEAM runtime | Same VM that powers WhatsApp, Discord, Ericsson |
| Compiles to JS | Run the same code in browsers |
| No null/exceptions | Explicit error handling, no surprises |
| Immutable data | Predictable state, easy concurrency |
| Friendly errors | Clear messages that help you fix issues |
Gleam vs Other Languages
| Aspect | Gleam | Elixir | Rust | TypeScript |
|---|---|---|---|---|
| Type system | Static | Dynamic | Static | Static |
| Runtime | BEAM | BEAM | Native | Node/Browser |
| Null safety | No nulls | Has nil | Option type | Optional |
| Learning curve | Gentle | Gentle | Steep | Moderate |
| Concurrency | Actor model | Actor model | Threads | Async/await |
| Error handling | Result type | Exceptions | Result type | Exceptions |
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
Popular Packages
| Package | Purpose |
|---|---|
gleam_json | JSON encoding/decoding |
gleam_http | HTTP client/server |
gleam_otp | OTP behaviors (GenServer, etc.) |
lustre | Frontend framework (like Elm) |
wisp | Web framework |
mist | HTTP 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?
| Reason | Details |
|---|---|
| Growing fast | 70% approval in Stack Overflow 2025, Thoughtworks Technology Radar |
| Type safety | Catch errors before they reach production |
| BEAM reliability | Same foundation as WhatsApp, Discord |
| Full-stack | One language for backend and frontend |
| Great tooling | Formatter, LSP, package manager included |
| Friendly community | Welcoming to beginners |
Summary
| Concept | Gleam Approach |
|---|---|
| Variables | Immutable with let |
| Functions | fn keyword, explicit types |
| Errors | Result and Option types |
| Data | Custom types and variants |
| Control flow | Pattern matching with case |
| Composition | Pipe operator |> |
| Modules | File-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
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
PostgreSQL Advanced Guide: From Queries to Performance Tuning
Master PostgreSQL with advanced SQL queries, indexing strategies, performance optimization, JSON support, and production database management.
DevOpsSurviving Tech Layoffs: A Developer's Complete Guide (2025)
Practical strategies for developers facing layoffs. From immediate action steps to long-term career resilience, this guide covers everything you need.
DevOpsCursor vs Claude Code: The Ultimate AI Coding Tools Comparison (2025)
In-depth comparison of Cursor and Claude Code for AI-assisted development. Features, pricing, workflows, and which one wins for different use cases.
Comments
Comments are powered by GitHub Discussions.
Configure Giscus at giscus.app to enable comments.