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
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.
Frequently Asked Questions
What is Gleam programming language?
Is Gleam better than Elixir?
Can Gleam be used for web development?
Why is Gleam gaining popularity in 2025?
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.
DevOpsKubernetes Helm Charts: Package and Deploy Applications
Master Helm for Kubernetes deployments. Learn chart creation, templates, values, dependencies, and best practices for production applications.
DevOpsAWS Lambda Serverless Guide: Build Scalable Applications
Master AWS Lambda for serverless computing. Learn function deployment, API Gateway integration, event triggers, and best practices for production.
Comments
Comments are powered by GitHub Discussions.
Configure Giscus at giscus.app to enable comments.