TL;DR:

Factory Functions are functions that build and return objects based on input parameters — just like a real-world factory. Unlike creating objects manually, they solve key issues like code repetition, poor maintainability, lack of scalability, and unprotected data structures. In this post, we explore how Factory Functions work, what problems they solve, and how they can be used both on the backend and frontend to write clean, secure, and scalable code.


Introduction

What is a Factory Function, why do we need it, and where is it used? Is this just a fancy term for explaining a dry theory about something unnecessary — or is it a truly useful concept for JavaScript developers?

Welcome to Episode 3 of the “Do You Know How It Works?” series!

In this post, we’ll take a fun and practical approach to understanding Factory Functions — what they are, what problems they solve, and how they can seriously improve the way we structure our code. And don’t worry, this isn’t just theory — we’re going to build examples step-by-step and apply it in a real use case.


In short, factory functions are just functions that build something and return what was built. That “something” could be an object, another function — anything.

In this article, we’ll focus on objects as our primary example.

“But Matheus, if I want to build an object, I can just use const obj = {...} and move on, right?”

Sure — and sometimes that’ll be enough. But as your application grows, that approach can start causing problems. Especially when you're creating lots of similar objects across different places.

To show you what I mean, let’s build a pet registration system for a pet shop. Along the way, we’ll explore the issues that come up with the “manual” approach.


Problem 1 — Code Repetition

const pet1 = {
  name: "Katze",
  ownerName: "Matheus",
  identify() {
    return `${this.name} belongs to ${this.ownerName}`;
  }
};

const pet2 = {
  name: "Hund",
  ownerName: "Pedro",
  identify() {
    return `${this.name} belongs to ${this.ownerName}`;
  }
};

const pet3 = {
  name: "Vogel",
  ownerNme: "Miguel", // Typo here
  identify() {
    return `${this.name} belongs to ${this.ownerName}`;
  }
};

console.log(pet1.identify()); // Katze belongs to Matheus
console.log(pet2.identify()); // Hund belongs to Pedro
console.log(pet3.identify()); // Vogel belongs to undefined

Houston, we have the first problem: Code repetition.

If you’ve read the article about inheritance, you know where this is going: repeating logic is a practice practice considered terrible, condemnable, abominable, disastrous, horrible, catastrophic… as Cap would say:

Captain America Meme

In this example, we’re duplicating the structure of the object and its method for each new pet. Every time I had to create a new pet, I had to write the entire object and repeat the logic. In the programming world, having to repeat logic multiple times in your code will cause a huge delay and make you more prone to errors. Notice that in the third pet, the attribute representing the owner's name was written as ownerNme and not ownerName. Seems like a silly mistake, but trust me — from experience — after 5–6 hours of straight coding, finding a typo is harder than finding a logic error.

“But mate, if I use Copy+Paste, it’ll be quick to create the objects and I won’t mistype the attribute name.”

Don't use useful resources to justify badly written code.

In a large-scale codebase, you might not mistype the attribute, but copying and pasting 800 different pets won’t be fast at all, it’s absolute madness.

Problem 1: Repeting code.


Problem 2 — Maintainability

Let’s say I want to rename the name property to petName, for better readability. Here’s what happens:

const pet1 = {
  petName: "Katze",
  ownerName: "Matheus",
  identify() {
    return `${this.petName} belongs to ${this.ownerName}`;
  }
};

const pet2 = {
  petName: "Hund",
  ownerName: "Pedro",
  identify() {
    return `${this.petName} belongs to ${this.ownerName}`;
  }
};

const pet3 = {
  petName: "Vogel",
  ownerName: "Miguel",
  identify() {
    return `${this.petName} belongs to ${this.ownerName}`;
  }
};

console.log(pet1.identify()); // Katze belongs to Matheus
console.log(pet2.identify()); // Hund belongs to Pedro
console.log(pet3.identify()); // Vogel belongs to Miguel

And thus, the second problem is on our path: Maintaining the code.
When I wanted to edit the objects, I had to find and change the attribute in each of them, and once again, I was at risk of making a mistake. Also, in a large-scale codebase, you may have many of these objects scattered throughout the application, in dozens or hundreds of places.

“But Matheus, could just use find-and-replace in my IDE and change all name for petName.”

Don't use useful resources to justify badly written code.
Sure, your IDE may indeed find all the name and replace with petName, but what if other objects — like employees — also have a name property?
Congratulations, your employees now have ✨petNames✨.

Problem 2: Code is hard to maintain.


Problem 3 — Scalability

Still in this line of modifying the object, now I want to add another parameter to my pet: the animal’s species. To do this, I’ll modify the object again.

const pet1 = {
  petName: "Katze",
  ownerName: "Matheus",
  species: "Cat",
  identify() {
    return `The ${this.species} ${this.petName} belongs to ${this.ownerName}`;
  }
};

const pet2 = {
  petName: "Hund",
  ownerName: "Pedro",
  species: "Dog",
  identify() {
    return `The ${this.species} ${this.petName} belongs to ${this.ownerName}`;
  }
};

const pet3 = {
  petName: "Vogel",
  ownerName: "Miguel",
  species: "Bird",
  identify() {
    return `The ${this.species} ${this.petName} belongs to ${this.ownerName}`;
  }
};


console.log(pet1.identify()); // The cat Katze belongs to Matheus
console.log(pet2.identify()); // The dog Hund belongs to Pedro
console.log(pet3.identify()); // The bird Vogel belongs to Miguel

Shine on us, oh mighty third problem: Scalability.
Again, to add this attribute, I had to go through each object and also modify each function inside those objects. So, in order to add features to my code, I had to go on a hunting adventure to find all objects in my codebase, and risk breaking something in the process.
A huge amount of work that could be avoided.

“But I…”

Dude, don't even start. You already know what I’ll say. 😅

Problem 3: Hard to evolve code.


Problem 4 — Lack of Protection

Let’s say we’ve got everything working and our system is live. Then someone (or some code) changes object values like this:

pet1.ownerName = "Matheus Julidori";
pet2.ownerName = "Pedro Henrique";
pet3.petName = "Miguel S.";

console.log(pet1.identify()) // The Cat Katze belongs to Matheus Julidori
console.log(pet2.identify()) // The Dog Hund belongs to Pedro Henrique
console.log(pet3.identify()) // The Bird Miguel S. belongs to Miguel

He presents himself bringing tons of bugs: The dreaded fourth problem, lack of security.
The object’s elements are all mutable. I can change them however I want, whenever I want. Not just me, but anyone or anything in the system.
Now we’ve got inconsistent data — names with and without last names, pet names that were meant for owners, and no restrictions to prevent it.

Problem 4: No data protection.


Summary of the Problems

  • Code repetition
  • Hard to maintain
  • Difficult to scale
  • No data protection

And I didn’t even started on subject of testing. If these 4 aren’t enough reasons to want a better solution... I really hope we’re not on the same team 😅
And by a better solution, I mean design patterns, like Factory Functions


Factory Functions to the Rescue

Think of a car factory: raw materials go in, a complete car comes out (more or less, but you get the point)
Factory functions follow the same principle: they take input (parameters) and return a fully structured object.

Let’s refactor:

function petFactory(petName, ownerName, species) {
  return {
    petName,
    ownerName,
    species,
    identify() {
      return `The ${this.species} ${this.petName} belongs to ${this.ownerName}`;
    }
  };
}

const pet1 = petFactory("Katze", "Matheus", "Cat");
const pet2 = petFactory("Hund", "Pedro", "Dog");
const pet3 = petFactory("Vogel", "Miguel", "Bird");

console.log(pet1.identify()) // The Cat Katze belongs to Matheus
console.log(pet2.identify()) // The Dog Hund belongs to Pedro
console.log(pet3.identify()) // The Bird Vogel belongs to Miguel

We can already see that the code is cleaner, more organized and readable, and without tons of repeated stuff. Now all the logic lives in one single place. Cleaner, easier to maintain, and error-proof. But still, it lacks security.


Let’s Add Security

We can enhance our factory with private variables and controlled updates:

function petFactory(petName, ownerName, species) {
  let _petName = petName;
  let _ownerName = ownerName;
  let _species = species;

  return {
    identify() {
      return `The ${this._species} ${this._petName} belongs to ${this._ownerName}`;
    },
    update(newName, newOwner, newSpecies, user) {
      if (user.role !== "admin") {
        console.warn("Access denied: only admins can update pet data.");
        return;
      }
      this._petName = newName;
      this._ownerName = newOwner;
      this._species = newSpecies;
    },
    getData() {
      return { petName: this._petName, ownerName: this._ownerName, species: this._species };
    }
  };
}

// Users
const admin = { name: "Matheus", role: "admin" };
const visitor = { name: "Pedro", role: "user" };

// Creating pet
const pet = petFactory("Katze", "Matheus", "Cat");

console.log(pet.identify()); // The Cat Katze belongs to caretaker Matheus

pet.updateData("Lion", "Matheus Julidori", "Feline", visitor); // Permission denied
console.log(pet.identify()); // The Cat Katze belongs to caretaker Matheus

pet.updateData("Feline", "Matheus Julidori", "Cat", admin); // OK
console.log(pet.identify()); // The Cat Feline belongs to caretaker Matheus Julidori

In this example, we created the variables _petName, _ownerName, and _species inside the function. The value of these variables (attributes) isn’t exposed, nor are the attributes themselves, so I can't modify them using something like pet._ownerName = "someone". The only way to change them is by using the atualizarDados() function, which is protected. We simulated a user with permissions — only system admins can use the function to alter data. This way, we solved all four previously discussed problems.

in JS, prefixing an attribute with _ is a convention to indicate they are private properties. But be aware that this doesn't make them inaccessible if you return them in the object. However, in classes, there is the # symbol which makes the properties properly private.


Frontend Example: Building Cards with Factories

You can also use factory functions on the frontend to render data in a structured way.

</span>
 lang="en">

   charset="UTF-8">
  Pet Registration
  
    body {
      font-family: sans-serif;
      padding: 20px;
      background-color: #f7f7f7;
    }

    form {
      margin-bottom: 20px;
      padding: 16px;
      background: white;
      border-radius: 8px;
      box-shadow: 0 2px 4px rgba(0,0,0,0.1);
      max-width: 400px;
    }

    label {
      display: block;
      margin-bottom: 8px;
      font-weight: bold;
    }

    input {
      width: 100%;
      padding: 8px;
      margin-bottom: 16px;
      border: 1px solid #ccc;
      border-radius: 4px;
    }

    button {
      padding: 10px 16px;
      background-color: #4CAF50;
      color: white;
      border: none;
      border-radius: 6px;
      cursor: pointer;
    }

    .card-pet {
      background: white;
      border: 2px solid #ccc;
      border-radius: 8px;
      padding: 12px;
      margin-bottom: 10px;
      max-width: 280px;
      box-shadow: 0 2px 4px rgba(0,0,0,0.1);
    }
  



  Pet Registration

   id="form-pet">
     for="petName">Pet Name
     type="text" id="petName" required>

     for="ownerName">Owner's Name
     type="text" id="ownerName" required>

     for="species">Species
     type="text" id="species" required>

     type="submit">Register Pet
  

   id="area-pets">

  
    function petFactory(petName, ownerName, species) {
      let _petName = petName;
      let _ownerName = ownerName;
      let _species = species;

      return {
        identificar() {
          return `The ${this._species} ${this._petName} belongs to ${this._ownerName}`;
        },
        getDados() {
          return {
            petName: this._petName,
            ownerName: this._ownerName,
            species: this._species
          };
        }
      };
    }

    function petCardFactory(pet) {
      const { petName, ownerName, species } = pet.getDados();

      const card = document.createElement("div");
      card.className = "card-pet";

      const title = document.createElement("h3");
      title.textContent = petName;

      const speciesEl = document.createElement("p");
      speciesEl.textContent = `Species: ${species}`;

      const ownerEl = document.createElement("p");
      ownerEl.textContent = `Owner: ${ownerName}`;

      card.appendChild(title);
      card.appendChild(speciesEl);
      card.appendChild(ownerEl);

      return card;
    }

    // Form logic
    const form = document.getElementById("form-pet");

    form.addEventListener("submit", function(event) {
      event.preventDefault();

      const petName = document.getElementById("petName").value;
      const ownerName = document.getElementById("ownerName").value;
      const species = document.getElementById("species").value;

      const newPet = petFactory(petName, ownerName, species);
      document.getElementById("area-pets").appendChild(petCardFactory(novoPet));

      form.reset();
    });
  


}



    Enter fullscreen mode
    


    Exit fullscreen mode
    




In the script, we used 2 factory functions — one to create pets, and another to create cards and add them to the DOM.Now you can dynamically build cards in the UI using structured, protected data.
Disclaimer: It’s not good practice to have factory functions manipulate other objects, so the logic to append the created card is not inside the factory. Otherwise, we’d be manipulating the DOM directly.

  
  
  Factory Functions, Classes, and Constructors
By now you’ve probably noticed:  
“This feels a lot like what we do with classes or constructor functions…”
And yes — they are very similar. And if you don't know what constructors and classes are, or which one should you use, don't worry, that’s exactly what we'll be covering in the next topics.
  
  
  Wrapping Up
Factory functions aren’t just a theoretical concept — they’re a powerful and flexible pattern you can use in real-world JavaScript codebases. Whether you’re building domain models, encapsulating logic, protecting sensitive data, or simply organizing your code more efficiently, factory functions offer a clean and elegant solution.They fix:
🔁 Code repetition
🛠️ Poor maintainability
📈 Scalability headaches
🔐 Lack of encapsulation
💬 Got a use case, tip, or question about factories?
Drop it in the comments — I’d love to talk more!📬 And don’t forget: follow me @matheusjulidori for more hands-on JavaScript tips, patterns, and dev-friendly content, every week!