voltar aos artigosfunctional programming
$cd~/artigos/pure-functions-javascript-funcoes-puras
Functional Programming
série 33 Conceitos JS·parte 26 de 38

Pure Functions: A Magia da Previsibilidade

20 minVanderlei José Alamino

"A função pura é como uma máquina de vendas perfeita: você coloca as moedas certas, aperta o botão certo, e sempre recebe exatamente o que espera — nunca surpresas, nunca defeitos."

Uma função pura segue duas regras simples: mesmos argumentos → mesmo resultado, e nenhum efeito colateral fora do retorno. Esse padrão torna código previsível, testável e seguro para cache.

snippetJavaScript
function calculateTotal(price, taxRate) {  return price + price * taxRate;} calculateTotal(100, 0.08);  // 108 — sempre

Neste guia da série 33 Conceitos JS — projeto open source criado por Leonardo Maldonado — você vai aprender:

  • As duas regras que definem pureza
  • Efeitos colaterais (side effects) e mutações acidentais
  • Padrões imutáveis com spread e structuredClone()
  • Quando impureza é inevitável — e como isolá-la nas bordas

Pré-requisitos: Higher-Order Functions e Closures — HOFs compõem funções puras pequenas em pipelines poderosas.

pure-functions-javascript-funcoes-puras.meta.jsonjson

// metadados do artigo

{
  "tag": "functional programming",
  "readTime": "20 min",
  "author": "Vanderlei José Alamino",
  "source": "33jsconcepts.com",
  "series": "33 Conceitos JS",
  "part": {
    "current": 26,
    "total": 38
  }
}
abrir fonte original
pure-functions-javascript-funcoes-puras.mdmarkdown

Fonte de referência

Conteúdo adaptado a partir do guia original em inglês.

33jsconcepts.com
01

seção

Analogia: A Receita Perfeita

Pense em uma função pura como uma receita de bolo. Mesmos ingredientes → mesmo bolo. A receita não depende da hora do dia, do clima ou do que você cozinhou ontem — e não reorganiza sua cozinha enquanto assa.

snippettext
FUNÇÃO PURA (Receita)          FUNÇÃO IMPURA (Chef imprevisível)─────────────────────          ────────────────────────────────Ingredientes → Receita → Bolo   Ingredientes → humor/clima → ???✓ Mesmo input = mesmo output    ✗ Mesmo input pode variar✓ Não muta a cozinha            ✗ Reorganiza tudo ao redor

Função pura = previsível, autocontida, confiável. Função impura = depende de fatores externos que você não controla.

02

seção

Regra 1: Mesma Entrada → Mesma Saída

Também chamada de transparência referencial (referential transparency): você poderia substituir a chamada pelo seu resultado e o programa se comportaria igual. É o que permite ao React cachear resultados com useMemo com segurança.

Math.max() é um ótimo exemplo:

snippetJavaScript
Math.max(2, 8, 5);  // 8Math.max(2, 8, 5);  // 8 — sempre // Substituir Math.max(2, 8, 5) por 8 não muda o programa.

O que quebra essa regra?

  • Aleatoriedade — Math.random()
  • Hora atual — new Date()
  • Estado externo — variáveis globais
snippetJavaScript
// ❌ IMPURA: aleatoriedadefunction getRandomDiscount(price) {  return price * Math.random();} // ❌ IMPURA: hora do diafunction getGreeting(name) {  const hour = new Date().getHours();  return hour < 12 ? `Bom dia, ${name}` : `Boa tarde, ${name}`;} // ❌ IMPURA: variável externalet taxRate = 0.08;function calculateTotal(price) {  return price + price * taxRate;}

Como corrigir

Passe tudo que a função precisa como argumentos:

snippetJavaScript
// ✓ PURAfunction calculateTotal(price, taxRate) {  return price + price * taxRate;} calculateTotal(100, 0.08);  // 108 — sempre

Teste rápido: você consegue prever a saída só olhando os inputs? Se precisar saber a hora, checar global ou executar para descobrir — provavelmente não é pura.

03

seção

Regra 2: Sem Efeitos Colaterais

Efeito colateral (side effect) = qualquer coisa além de computar e retornar um valor. A função altera o mundo fora dela.

Efeito colateralExemploProblema
Mutar inputarray.push(item)Quem ainda usa o array vê mudança inesperada
Variável externacounter++Dependência oculta
Consoleconsole.log()I/O além do retorno
DOMel.innerHTML = ...Muda a página
HTTPfetch(...)Sistema externo
StoragelocalStorage.setItem()Persistência fora da função
snippetJavaScript
// ❌ IMPURA: múltiplos efeitos colateraisfunction processUser(user) {  user.lastLogin = new Date();  console.log(`Usuário ${user.name}`);  userCount++;  return user;} // ✓ PURA: retorna novos dadosfunction processUser(user, loginTime) {  return { ...user, lastLogin: loginTime };}

console.log() é impuro? Tecnicamente sim — é I/O. Na prática, aceitável para debug. Não deixe logs em lógica de negócio que deveria ser pura.

04

seção

O Erro #1: Mutações Acidentais

A forma mais comum de criar impureza sem querer é mutar objetos ou arrays passados como argumento.

snippetJavaScript
// ❌ IMPURA: muta o carrinho originalfunction addItem(cart, item) {  cart.push(item);  return cart;} const myCart = ["maçã", "banana"];const newCart = addItem(myCart, "laranja"); console.log(myCart);   // ["maçã", "banana", "laranja"] — surpresa!console.log(myCart === newCart);  // true — mesmo array
snippetJavaScript
// ✓ PURA: novo array, original intactofunction addItem(cart, item) {  return [...cart, item];} const myCart = ["maçã", "banana"];const newCart = addItem(myCart, "laranja"); console.log(myCart);   // ["maçã", "banana"]console.log(newCart);  // ["maçã", "banana", "laranja"]console.log(myCart === newCart);  // false
05

seção

A Armadilha da Cópia Rasa

O spread (...) cria apenas cópia rasa (shallow copy). Objetos aninhados ainda são compartilhados:

snippetJavaScript
const user = {  name: "Alice",  address: { city: "NYC", zip: "10001" },}; const updatedUser = { ...user, name: "Bob" }; console.log(user === updatedUser);  // false — topo é novo updatedUser.address.city = "LA";console.log(user.address.city);  // "LA" — original mutou!

Para aninhados, use structuredClone() ou spread em cada nível:

snippetJavaScript
// ✓ Clone profundoconst updatedUser = {  ...structuredClone(user),  name: "Bob",}; updatedUser.address.city = "LA";console.log(user.address.city);  // "NYC" — seguro

Limitação: structuredClone() não clona funções nem nós DOM — lança DataCloneError nesses casos.

06

seção

Padrões Imutáveis

Atualizar objeto

snippetJavaScript
// ❌ user.email = email// ✓function updateEmail(user, email) {  return { ...user, email };}

Adicionar ao array

snippetJavaScript
function addTodo(todos, newTodo) {  return [...todos, newTodo];}

Remover do array

snippetJavaScript
function removeTodo(todos, index) {  return todos.filter((_, i) => i !== index);}

Atualizar item no array

snippetJavaScript
function completeTodo(todos, index) {  return todos.map((todo, i) =>    i === index ? { ...todo, completed: true } : todo,  );}

Ordenar sem mutar

snippetJavaScript
// ❌ numbers.sort() muta o originalfunction getSorted(numbers) {  return [...numbers].sort((a, b) => a - b);} // ✓ ES2023+: toSorted() retorna novo arrayfunction getSortedModern(numbers) {  return numbers.toSorted((a, b) => a - b);}

O ES2023 adicionou toSorted(), toReversed(), toSpliced() e with() — perfeitos para funções puras. Verifique suporte de navegador antes de usar em produção.

07

seção

Por que Funções Puras Importam

1. Mais fáceis de testar

Sem mocks, setup ou cleanup — input → output:

snippetJavaScript
function add(a, b) { return a + b; } expect(add(2, 3)).toBe(5);expect(add(-1, 1)).toBe(0);

2. Mais fáceis de debugar

Saída errada? O bug está nessa função — não em estado global alterado à distância.

3. Seguras para cache (memoização)

Mesma entrada → mesma saída → você pode cachear o resultado. Impuras não — podem precisar retornar valores diferentes mesmo com mesmos args.

4. Seguras para paralelização

Sem estado compartilhado — chunks podem rodar em paralelo:

snippetJavaScript
const results = await Promise.all([  processChunk(data.slice(0, 1000)),  processChunk(data.slice(1000, 2000)),  processChunk(data.slice(2000, 3000)),]);

5. Mais fáceis de entender

Tudo que a função faz está no código — sem dependências ocultas.

snippetJavaScript
function formatPrice(cents, currency = "BRL") {  return new Intl.NumberFormat("pt-BR", {    style: "currency",    currency,  }).format(cents / 100);}

Intl.NumberFormat não quebra pureza: mesmos inputs → mesma string formatada.

08

seção

Quando Pureza Não É Possível

Apps reais precisam de DOM, HTTP, storage, logs e eventos. A estratégia: empurrar impureza para as bordas e manter o núcleo puro.

snippettext
BORDAS (impuras)     NÚCLEO (puro)        BORDAS (impuras)────────────────     ─────────────        ────────────────Ler DOM/API/input → Transformar/validar → Escrever DOM/API/console
snippetJavaScript
// IMPURO: lê DOMfunction getUserInput() {  return document.querySelector("#username").value;} // PUROfunction formatUsername(name) {  return name.trim().toLowerCase();} function isValidUsername(name) {  return name.length >= 3 && name.length <= 20;} // IMPURO: escreve DOMfunction displayError(message) {  document.querySelector("#error").textContent = message;} function handleSubmit() {  const raw = getUserInput();  const formatted = formatUsername(raw);  const isValid = isValidUsername(formatted);   if (!isValid) {    displayError("Username deve ter 3-20 caracteres");  }}

formatUsername e isValidUsername são triviais de testar. A impureza fica isolada onde é fácil encontrar.

09

seção

Perguntas Clássicas de Entrevista

10

seção

Teste Seu Conhecimento

$ summary --quick

Resumo rápido

  • Duas regras: mesma entrada → mesma saída; sem efeitos colaterais
  • Efeitos colaterais: mutação, I/O, DOM, HTTP, random, hora atual
  • Mutação #1 armadilha — retorne novos dados com spread ou structuredClone()
  • Cópia rasa não protege objetos aninhados
  • ES2023: toSorted(), toReversed(), with() — arrays imutáveis
  • Testes triviais — input/output, sem mocks
  • Memoização segura — mesma entrada sempre = mesmo resultado
  • Impureza nas bordas — núcleo puro, I/O isolado
  • console.log é impuro — ok para debug, não para lógica de negócio

Créditos da série

Este artigo faz parte de uma série de estudos inspirada no projeto 33 Conceitos JS, criado por Leonardo Maldonado. Todo o conteúdo original está disponível em 33jsconcepts.com.