Introduction to DRY

A brief article explaining what DRY is in programming and how it can benefit your codebase

DRYCoding principles

September 13, 2025


Take a look at this code for obtaining a list of packages that the current user of a logistics company has to deliver:

const myDeliverablePackages = packages
	// packages have been inspected and are safe to deliver
	.filter(package => package.inspected && package.safe)
	// packages are ready for delivery
	.filter(package => package.status === Statuses.READY_FOR_DELIVERY)
	// packages are assigned to the current logged in user
	.filter(package => package.assignedOperator === currentUser.id)

As it turns out, we may need to get this specific list of packages in several places throughout our codebase (maybe once to display this list in the clientside, twice to send it as the payload of a confirmation email, thrice if we also want to take the same list and use it as a payload to change the status of these packages to BEING_DELIVERED, etc). If we just copy and paste the same code it starts to get repetitive

// ...  
// ... We add it to the React component that displays this list

const myDeliverablePackages = packages
	// packages have been inspected and are safe to deliver
	.filter(package => package.inspected && package.safe)
	// packages are ready for delivery
	.filter(package => package.status === Statuses.READY_FOR_DELIVERY)
	// packages are assigned to the current logged in user
	.filter(package => package.assignedOperator === currentUser.id)

// ...  
// ... and for the code that prepares the payload for a confirmation email
const myDeliverablePackages = packages
	.filter(package => package.inspected && package.safe)
	.filter(package => package.status === Statuses.READY_FOR_DELIVERY)
	.filter(package => package.assignedOperator === currentUser.id)

// ...  
// ... and for the code that prepares the payload to change the state
const myDeliverablePackages = packages
	.filter(package => package.inspected && package.safe)
	.filter(package => package.status === Statuses.READY_FOR_DELIVERY)
	.filter(package => package.assignedOperator === currentUser.id)

   
// ...
// ... and in several other places
const myDeliverablePackages = packages
	.filter(package => package.inspected && package.safe)
	.filter(package => package.status === Statuses.READY_FOR_DELIVERY)
	.filter(package => package.assignedOperator === currentUser.id)

// ...

const myDeliverablePackages = packages
	.filter(package => package.inspected && package.safe)
	.filter(package => package.status === Statuses.READY_FOR_DELIVERY)
	.filter(package => package.assignedOperator === currentUser.id)


// ...
// ... you get the idea
// ...

However! There comes a time on every coder's career where they open their eyes to an interesting realization: Writing the same code ten times in ten different places feels kind of dumb.

Not only because sometimes the code for the functionality you're including is quite lengthy so lengthy x 10 = a lot of repeated code; but also because whenever you want to improve or enhance what that code does you need to make sure you do it in all ten places!

If you don't keep all ten code blocks updated in the same way, you've now created ten different pieces of code that basically do the same thing in slightly different ways and thus made it harder for yourself or your peers to maintain it.

And that may be alright if you're a vibe coder building a 10MM SaaS every day while sitting at the pool leaving it all to c*rsor or w*ndsurf! but for people that are *actually* paying attention to the code that they write this can get incredibly tedious, and bugs can very easily be introduced.

This is where a simple concept may either come instinctively, or be passed down by more senior coworkers: Don't. Repeat. Yourself. (DRY!)

You might already grasp the idea behind "Don't repeat yourself": Don't repeat yourself.

Let's see how that would look if we apply it to the code snippet we saw before:

// Newly created function to encapsulate the logic 
// to get the list of packages that we care about

function getPackagesToDeliver(packages, userId){
  return packages
      // packages have been inspected and are safe to deliver
      .filter(package => package.inspected && package.safe)
      // packages are ready for delivery
      .filter(package => package.status === Statuses.READY_FOR_DELIVERY)
      // packages are assigned to the chosen userId 
      .filter(package => package.assignedOperator === userId)
}

Great! we now put the code we had repeated in a lot of places inside a single function.

✨Coding✨

We can now modify every repeated code block that we had that needed to accomplish this same thing and end up with something like this:


// ... We add it to the React component that displays this list
const myDeliverablePackages = getPackagesToDeliver(packages, currentUser.id);


// ... and for the code that prepares the payload for a confirmation email
const myDeliverablePackages = getPackagesToDeliver(packages, currentUser.id);
    
    
// ... and for the code that prepares the payload to change the state
const myDeliverablePackages = getPackagesToDeliver(packages, currentUser.id);
    
// ...
// ... and in several other places, you get the idea
// ...

The amount of benefits we get from doing this is unmeasurable, though, since now:

  • We can (re)use the same logic in a lot of places just by calling this single function
  • Wherever we call it, we only need to think about the concept of getting a list of packages
  • It is way easier to test this code and every other function that calls it
  • Stakeholders will love us since it's faster to make changes in a single place instead of 10
  • We can add DRY to the list of skills in our resume (+1 Hireability)
  • We can make our own blog post about it (+1 Speech)

Do keep in mind that sometimes we can take it too far: if we try to avoid repeating every minuscule amount of code as soon as we use it in more than one place, or worse- if we try to preemptively apply DRY without a good reason, then we may be unintentionally making things just as complicated as if we always repeated every code block.

For example, someone may look at the code we saw before and be tempted to do this:


// We surely need the entire list of packages in several places, 
// so a function to return the entire list of packages makes sense right?
function getPackages(packages){

	return packages;
}

// We surely need to know which packages have been inspected in several places,
// so a function to return the packages that have been inspected makes sense right?
function getInspectedPackages(packages){

	return packages.filter(package => package.inspected);
}

// We surely need to know which packages have been inspected in several places,
// so a separate function to return the packages that are safe makes sense right?
function getSafePackages(packages){

	return packages.filter(package => package.safe);
}

// We surely need to know which packagesare ready for delivery in several places,
// so a separate function to get packages ready for delivery makes sense right?
function getReadyForDeliveryPackages(packages){

	return packages.filter(package => package.status === Statuses.READY_FOR_DELIVERY);
}

// We surely need to know which packages are assigned to a specific user,
// so a separate function to get the packages assigned to a specific user sense right?
function getUserPackages(packages, userId){

	return packages.filter(package => package.assignedOperator === userId);
}


// Same function as before that groups the logic we need,
// but now it's harder to read, debug and reason about
function getPackagesToDeliver(packages, userId){

	const packageList = getPackages(packages);
    const inspectedPackageList = getInspectedPackages(packageList);
    const getInspectedSafePackageList = getSafePackages(inspectedPackageList);
    //ISRFD = (I)nspected (S)afe (R)eady (F)or (D)elivery
    const getISRFDPackageList = getReadyForDeliveryPackages(getInspectedSafePackageList);
   	const getUserISRFDPackageList = getISRFDPackageList(getISRFDPackageList, userId);
    
	return getUserISRFDPackageList;
}

After this, we now need to go through 5 other functions if we want to understand (and possibly modify!) the logic needed for us to get this information, or to debug the entire thing if we detect an issue and need to fix it.

So yeah, try to adequately think about the tradeoffs regarding effort, overhead, maintainability and convenience for your codebase when assessing whether you should "un-repeat" a code block into a separate reusable piece just because it could be used more than once, or twice, or thrice because as we saw on that example DRY can definitely go too far!

Back to top