This article explains “Pure Functions”, not “Idempotent Functions”. I have written a new article to provide a more accurate description of “Idempotent” and also “Pure Functions” here:
My thanks to Phil Atkins in Cambridge, UK for making me aware of my error.
A method is idempotent when the same input(s) always return the same output. This method is idempotent:
This method is not idempotent:
When adding methods to classes, many developers spend little time deciding if the method should be idempotent. Making use of the properties in the class causes the method to not be idempotent, as in this example of the method named ReturnNumberOfDaysSincePersonWasBorn:
Here is the ReturnNumberOfDaysSincePersonWasBorn method re-written to be idempotent:
And here is another version of the method that is not idempotent. This time, the method is not idempotent because it is changing the value of a variable (Age) scoped outside of the method.
Here are the advantages to an idempotent method:
- The methods are easier to maintain. They are easier to maintain because a developer needs to spend less time analyzing the impacts of changes to the method. The developer does not have to consider the state of other inputs that are used in the method. When considering a change to a method that is not idempotent, the developer must think about the impact on properties used in the method that were not passed in.
- The methods are easier to test. When writing a unit test, it is easy to pass values into the method and write the test to verify the correct output. But writing tests on non-idempotent methods take longer and are more complicated because more setup or injection of values is necessary for the test.
- The methods are easier to re-use. You can call the method from other places in the module, or call the method from several different modules easily when the method is idempotent.
- The methods are easier to move to other classes. If you have an idempotent method in one class and you want to move it to a utility class to be used by other modules, it is easy to do so.
- The methods are more likely to be thread-safe. Idempotent methods don’t reference variables in shared memory that are being referenced by other threads. Caveat: variables such as objects that are passed by reference could still experience threading problems when used inside of static/idempotent methods.
Resist the Temptation!
When you encounter a case like the one below, where Method3() needs the BirthDate for a calculation, it can be tempting to change the methods from static to be non-static and to reference the BirthDate property in Method3(). The other alternative is to pass the BirthDate into Method1(), and from Method1() to Method2(), and from Method2() to Method3(). Although we don’t like to make those changes, doing so allows us to keep our idempotent method and the advantages that provides.
In C#, if you are unable to mark your method “static”, then it is probably not idempotent. If the method makes database calls, API calls, updates properties outside of the method, or uses variables that have scope outside of the method, then it is not idempotent.