This article will help you convert a simple Java program to Solidity to solve a simple integer sorting problem, highlighting many of the perils and pitfalls to avoid in optimizing gas usage — especially pre-mature optimization and algorithm validation.

On May 23rd, 2018 Ethereum’s Nick Johnson posted the first-ever Solidity Gas Golfing Contest on reddit.

Code golf contests are great because they encourage mastery of the intricacies of systems and programming languages in order to create the shortest possible source code to complete a given task. In the case of Ethereum and Solidity, this contest is all about minimizing the gas spent performing operations on the Ethereum Virtual Machine (EVM), rather than the brevity of the code itself.

Why is that important? At the time of writing each Ethereum block has an upper gas limit of 8 million. The minimum transaction requires 21,000 gas, so there’s a ceiling of about ~380 contract calls per block, and each block is processed every 15 seconds on average. Since every contract operation costs gas (adding numbers, iterating over arrays, creating new contracts, transferring balances, etc) minimizing the gas used is critical to writing executable code on the Ethereum blockchain.

Bad or inefficient code can kill your contract viability or bring the Ethereum network to its knees, for instance: The Inside Story of the CrytoKitties Congestion Crisis.

As someone who’s been programming in Solidity for a while now, I found the contest a refreshing way to rethink what I know and challenge a lot of assumptions. The first thing I focused on was the Sorting Integers problem, which will be the focus of this article.

Setting Up

Each challenge in the contest is contained in either a truffle project or an embark project. As a truffle user I’ll be using the code provided here: https://github.com/Arachnid/sggc.

If you’re new to Solidity coding, there’s an excellent guide to getting started with your first truffle project here: The Complete Tutorial On Getting Started With Smart Contracts On Ethereum.

Once you complete the tutorial above you’re ready to jump directly into working on these challenges.

Challenge #1: Sorting Integers

Each challenge in the golf contest is split into two components: Standard and Wild.

The Standard challenge is just pure Solidity without any in-line bytecode. A Standard submission is automatically added to the Wild list as well, and contracts that execute the least amount of gas are the winners. Wild, as mentioned above, include EVM bytecode in areas that might be better tuned by hand and deep-knowledge rather than relying on the Solidity compiler.

The first challenge is to take a list of n unsigned integers and sort them in ascending order. That’s it! They might be in reverse order, random-order, or the list of integers may be empty: just return a sorted (or empty) array.

The Contract: Sort.sol

The initial contract provided contains an empty method called sort that has a single parameter input which is an array of unsigned integers, and the function is intended to return an array of unsigned integers.

If you compile and run this right now in truffle ( truffle compile; truffle test test/Sort.js ) it’ll issue the following error:

/contracts/Sort.sol:21:19: Warning: Unused function parameter. Remove or comment out the variable name to silence this warning. function sort(uint[] input) public pure returns(uint[]) { ^----------^

In other words, it’s expecting a returned uint[].

The simplest first step is to simply return the incoming uint[] called input since that’s the array we’ll be operating on.

Running the test suite now will show us a list of each of the (four) tests and how their results differ from the expected results.

Now we have a good starting point to implement a sorting algorithm on the input variable.

Quicksort

This article doesn’t focus on “the best” solution for the Integer Sorting problem; our goal is to just establish a known-good algorithm in an established language (Java), and highlight the techniques used to convert it into Solidity.

For our purposes then let’s implement Quicksort, an algorithm that mixes together efficiency with comprehensibility in a great ratio. In other words, it’s easy to understand, easy to code, and is reasonably fast for average cases.

For our purposes we’re going to adapt the example implementation here: https://www.geeksforgeeks.org/quick-sort/

This implementation uses two methods: a recursive sort() function and a partition() function. Quicksort chooses a “pivot point” and comparison sorts smaller and smaller partitions until the entire array is sorted.

The beauty of this example is that it operates in-place on the existing array, so we don’t need to create any new data-structures.

Method Stubs

Let’s begin by creating empty method stubs emulating the intention of the Java version.

Java: void sort(int arr[], int low, int high) { ... }

Solidity: function quickSort(uint[] arr, int low, int high) internal pure {} Java: int partition(int arr[], int low, int high) { ... }

Solidity: function partition(uint[] arr, int low, int high) internal pure returns(int) {

return 0; // stub value

}

The intention of the Java sort() method is to operate in-place on the input array, and recursively calls itself on partitions of the array. It doesn’t have any output: in Java this is denoted with the void keyword, but in Solidity you simply omit a list of return values.

Two new keywords are introduced in the method signature: internal and pure . For the purposes of our algorithm we don’t want this private/internal method to be called from anything but the originating contract’s public method (also called sort() ).

Solidity supports polymorphism and all methods are virtual, so our new sort(uint[], int, int) method with the low and high integers will be called by the first sort(uint[]) method.

The pure keyword denotes that this method (and by extension, other invoked methods) will NOT change the contract state in any way. No member fields are altered. No dynamic arrays in “storage” are accessed. This is meant for methods executing math and math alone, only operating on the input and not the contract stored values.

The intention of the partition() function is to find an optimal pivot point for the recursive sorting method to operate on the two new partitions. It returns an integer, and the only new addition is that the return type is specified in the tail-end of the method signature, not up-front like Java.

Additionally, the return attribute is a list of values, and thus a function can assign multiple output values to multiple variables like so:

(var1, var2, var3) = multiValueOutput();

Since the function needs to return a value we put a stub return 0; as a temporary place-holder. This will be replaced shortly.

Finally, to set us up to start testing our implementation, let’s invoke the new sort function even if it doesn’t do anything.

The invocation of sort(uint[], int, int) in this case is expecting the boundaries for the partition we’re sorting. To begin with, it’s the entire array, so the low is 0 and the high is the number of elements minus one (the index of the last item in the array).

Our implementation should look something like this:

Why are we using bounds that are of type int if we’re operating on an unsigned integer ( uint ) array? Does Solidity auto-cast int to uint for us if we’re using array indexes?

No. In fact, in the rest of the algorithm we’ll have to explicitly cast int to uint when accessing elements in the array.

Why not use uint then, besides the fact that the Java algorithm uses signed integers? Because of this: sort(arr, low, pi-1);

Important Note: Prevent Underflow and Overflow

Any operation that does subtraction in Solidity on a uint has the possibility of underflowing. If the value is 0 and you subtract 1, what is the value now? It’s not negative one. It’s 2²⁵⁶-1. Is that number greater than 0? Yes. Therefore, if you have a loop counting on the number being greater than or equal to 0 it will ALWAYS be true.

That’s a great way to write a contract that consumes all its gas while it’s desperately trying to decrement a value in an infinite loop.

Let’s keep this in the front of our thoughts as we adapt the Java example.

sort(uint[] arr, int low, int high)

The Java example for this method is very short:

void sort(int arr[], int low, int high) {

if (low < high) {

int pi = partition(arr, low, high);

sort(arr, low, pi-1);

sort(arr, pi+1, high);

}

}

Looking this over, nothing really stands out as Solidity specific besides the already implemented method signature. The difference here being that it’s an unsigned integer array uint[] instead of integer.

Solidity handles complex data-structures in a similar way to Java. There aren’t any pointers, so an array of integers is passed by reference. The array arr[] is not a copy of input[] — it is input[] ; thus, any changes to arr[] in sort(uint[], int, int) are going to be passed out as the result of sort(uint[]) .

If we copy-paste the internals exactly and run our tests it will compile (with some warnings) and the test suite will generate stack overflows.

Ethereum has a stack size limit of 1,024. And since our partition method is empty and our current sort methods keep recursively calling themselves without any exit conditions.

We’d better implement that partition function!

partition(uint[] arr, int low, int high)

The partition function has a few jobs:

Set an initial pivot point (this one chooses the highest value rather than the middle)

Find the proper place for the pivot in the sorted array, and swap it for the value already there

Return a new pivot point

Note: Quicksort algorithms vary in their implementation of pivots. As noted above this one chooses the highest element; the efficiency of this is dependent on the dataset being sorted.

The Java implementation is as follows:

int partition(int arr[], int low, int high) {

int pivot = arr[high];

int i = (low-1); // index of smaller element



for (int j=low; j<high; j++) {

// If current element is smaller than or

// equal to pivot

if (arr[j] <= pivot) {

i++;



// swap arr[i] and arr[j]

int temp = arr[i];

arr[i] = arr[j];

arr[j] = temp;

}

}



// swap arr[i+1] and arr[high] (or pivot)

int temp = arr[i+1];

arr[i+1] = arr[high];

arr[high] = temp; return i+1;

}

Immediately we should remember the fact that we’re using integers for the bounds instead of unsigned integers (required to access an array index in Solidity).

We’re going to have to cast those values at the time of array access (rather than convert them to uint at the start of the function) so we can keep doing integer math (with possible negative values) in the loop.

Casting in Solidity is simple for these types uint(i) will do fine.

The updated code will look like this:

function partition(uint[] arr, int low, int high) internal pure returns(int) {

int pivot = int(arr[uint(high)]);

int i = (low-1); // index of smaller element for (int j = low; j < high; j++) {

if (int(arr[uint(j)]) <= pivot) {

i++; uint temp = arr[uint(i)];

arr[uint(i)] = arr[uint(j)];

arr[uint(j)] = temp;

}

} uint temp2 = arr[uint(i+1)];

arr[uint(i+1)] = arr[uint(high)];

arr[uint(high)] = temp2;



return i+1;

}

We had to make several changes here:

The pivot is an int so we had to access its position with a uint, then convert the uint to an int

is an so we had to access its position with a uint, then convert the uint to an int All the array accesses have to be uint

The temporary variables used for swapping are uint because the array is uint

Does it compile? Yes! Let’s run our test suite:

It worked!

As you can see the algorithm is correct and works fine, but is incredibly slow. It burned more than 7 million gas — about ~91% of one block’s entire ability to run calculations. I even had to update the data/Sort.json to increase the simulated gas caps just to get it to finish the tests.

Swapping

Everything costs gas in Ethereum, even creating temp variables. Did you notice that I created a temp2 variable? Variables in a method have method-wide scope so I can’t redeclare temp after the for loop.

temp2 only exists for illustrative purposes, based on temp still persisting outside of the for loop I could’ve just recycled it.

But Solidity doesn’t require you to use this nastiness. In the same way that method returns can be a list of values, we can assign multiple r-values to multiple l-values:

// Java-ish

uint temp = arr[uint(i)];

arr[uint(i)] = arr[uint(j)];

arr[uint(j)] = temp; // Solidity-ish

(arr[uint(i)], arr[uint(j)]) = (arr[uint(j)], arr[uint(i)]);

Note the beautiful in-place swapping above. Is it just syntactic sugar or does it actually save us some gas?

We’ll do the same with the second part and re-run our test:

(arr[uint(i+1)], arr[uint(high)]) = (arr[uint(high)], arr[uint(i+1)]);

We saved 116,454 gas!

That’s still far too much gas to sort a couple hundred integers, but it demonstrates that Solidity language constructs do make a difference in the compiled bytecode and gas consumption. This is certainly a comment on the Solidity compiler itself lacking optimization cases for common swap techniques.

Being Clever with XOR Swap

As a further demonstration, since we’re using unsigned integers, let’s try this without the Solidity language construct or the temporary variable. Let’s dust off the old XOR swap algorithm.

// Solidity (clean)

(arr[uint(i)], arr[uint(j)]) = (arr[uint(j)], arr[uint(i)]); AND (arr[uint(i+1)], arr[uint(high)]) = (arr[uint(high)], arr[uint(i+1)]); BECOME // Solidity (XOR swap)

arr[uint(i)] ^= arr[uint(j)];

arr[uint(j)] ^= arr[uint(i)];

arr[uint(i)] ^= arr[uint(j)]; arr[uint(i+1)] ^= arr[uint(high)];

arr[uint(high)] ^= arr[uint(i+1)];

arr[uint(i+1)] ^= arr[uint(high)];

There’s a great proof of why this works in the Wikipedia article. With the utmost faith in mathematics let’s run our new code that replaces the multi-value swap with XOR swaps.

Well, that’s not what we were expecting. Something is causing the method to spend all the gas (an infinite loop? hint: it’s almost always an infinite loop).

Reading the Wikipedia article very carefully we stumble upon this insight from their own C example:

Note that the code does not swap the integers passed immediately, but first checks if their addresses are distinct. This is because, if the addresses are equal, the algorithm will fold to a triple *x ^= *x resulting in zero.

Ah. i and j or i+1 and high might be the same address in the array. Let’s avoid that edge case:

if (i != j) {

arr[uint(i)] ^= arr[uint(j)];

arr[uint(j)] ^= arr[uint(i)];

arr[uint(i)] ^= arr[uint(j)];

} if (i+1 != high) {

arr[uint(i+1)] ^= arr[uint(high)];

arr[uint(high)] ^= arr[uint(i+1)];

arr[uint(i+1)] ^= arr[uint(high)];

}

Re-run our test:

Wow! We just saved another 2,823,204 gas by using XOR swapping with unsigned integers.

Wikipedia specifically calls out avoiding the usage of XOR swapping these days because modern compilers can detect simple swaps that are safe to optimize like this, and do so: does the Solidity compiler not do this? (spoiler: the article’s ending might surprise you!)

Let’s keep looking at ways to simplify and optimize this Solidity implementation.

Method calls

Do we really need two separate methods to handle recursion and partitioning? No.

Do method calls cost gas? Yes.

Therefore, we should do what we can to limit the number of method calls needed for the EVM. This is the opposite of conventional programming style in which large functions should be broken up into smaller functions for ease of maintenance.

There’s no hard and fast rule about this, obviously — don’t trust in black and white value judgments with code. In our case, with a code golf contest, and the fact that everything costs gas in the EVM, we want to limit the need for unnecessary things.

If we can make the code understandable with comments, utilize a clean layout, and avoid nesting too deeply, then keeping invoked method counts low is the preference.

Let’s refactor the sort(uint[], int, int) method and integrate the contents of the partition function. To review, we’re starting with:

function sort(uint[] arr, int low, int high) internal pure {

if (low < high) {

int pi = partition(arr, low, high);

sort(arr, low, pi-1);

sort(arr, pi+1, high);

}

}

The line assigning the partition value is where we need to integrate the contents of the partition() method.

All we really need to do here is update the variable names to be consistent:

function sort(uint[] arr, int low, int high) internal pure {

if (low < high) {

int pivot = int(arr[uint(high)]);

int i = (low-1); // index of smaller element for (int j = low; j < high; j++) {

if (int(arr[uint(j)]) <= pivot) {

i++; if (i != j) {

arr[uint(i)] ^= arr[uint(j)];

arr[uint(j)] ^= arr[uint(i)];

arr[uint(i)] ^= arr[uint(j)];

}

}

} if (i+1 != high) {

arr[uint(i+1)] ^= arr[uint(high)];

arr[uint(high)] ^= arr[uint(i+1)];

arr[uint(i+1)] ^= arr[uint(high)];

} pivot = i + 1;

sort(arr, low, pivot-1);

sort(arr, pivot+1, high);

}

}

We’ve updated the variable name pi to pivot and made sure the return value that partition () used to provide is accounted for in pivot = i + 1 .

Running the test again we receive a new gas cost of 4,367,460 — a savings of 21,460 gas. Nothing to write home about, but again a clear indication of the cost of internal method calls.

Recurse Then Check, or Check Then Recurse?

One thing that might stand out to the reader is that every time our sorting algorithm runs we immediately check to see if low is less than high . This means in the worst case if its not we still invoked a method call cost just to get to that conditional checkpoint.

Let’s refactor it to do the check before calling further iterations of the sorting method.

function sort(uint[] arr, int low, int high) internal pure {

int pivot = int(arr[uint(high)]);

int i = (low-1); // index of smaller element for (int j = low; j < high; j++) {

if (int(arr[uint(j)]) <= pivot) {

i++; if (i != j) {

arr[uint(i)] ^= arr[uint(j)];

arr[uint(j)] ^= arr[uint(i)];

arr[uint(i)] ^= arr[uint(j)];

}

}

} if (i+1 != high) {

arr[uint(i+1)] ^= arr[uint(high)];

arr[uint(high)] ^= arr[uint(i+1)];

arr[uint(i+1)] ^= arr[uint(high)];

} pivot = i + 1; if (low < pivot-1) {

sort(arr, low, pivot-1);

} if (pivot+1 < high) {

sort(arr, pivot+1, high);

}

}

The most important segment is at the bottom: we can’t compare low to high anymore because we’re trying to base the new recursion on the pivots. So if we’re going to avoid always invoking a recursion in favor of checking beforehand we need to do two new conditional checks matching the input of the recursion calls.

That’s a lot of code and it might make you suspicious that it will cost more to do this than leaving it the old way.

When we compile and test, however, we don’t know: test vector 0 now fails.

It turns out that test vector 0 is an empty array. Ah. We can’t operate on an empty array!

A minor update to the contract’s public interface, the sort(uint[]) method can fix this:

if (input.length >= 2) {

sort(input, 0, int(input.length - 1));

}

We don’t need to sort an empty array or an array with only one element, right?

Re-running out test yields a new gas cost of 4,345,785 — a new savings of 21,675 gas. It appears that even with all of our “extra” code we’ve managed to use even less gas than before.

Can We Avoid So Many Casts?

All over the place we’re stuck using uint() and int() casts because we inherited a few subtraction operations from the Java example. Yet at no point are we ever subtracting more than 1 from the values. Can we refactor around this limitation?

Let’s start by just removing the casts and updating the indexing variables with unsigned integers. We’ll have to update the method signatures and invocations as well:

function sort(uint[] input) public pure returns(uint[]) {

if (input.length >= 2) {

sort(input, 0, input.length-1);

} return input;

} function sort(uint[] arr, uint low, uint high) internal pure {

uint pivot = arr[high];

uint i = low - 1; // DANGER: Underflow? for (uint j = low; j < high; j++) {

if (arr[j] <= pivot) {

i++;



if (i != j) {

arr[i] ^= arr[j];

arr[j] ^= arr[i];

arr[i] ^= arr[j];

}

}

} if (i+1 != high) {

arr[i+1] ^= arr[high];

arr[high] ^= arr[i+1];

arr[i+1] ^= arr[high];

} pivot = i + 1;

if (low < pivot-1) { // DANGER: Underflow?

sort(arr, low, pivot-1);

} if (pivot+1 < high) {

sort(arr, pivot+1, high);

}

}

It does compile, but fails test vector 3: “ invalid opcode .” Is it accessing an non-existent array element? How can we account for the possible underflow error?

One thought, again, is that we’re only subtracting a single value of 1 from the numbers, so we could put a check on the actual underflowed value itself: 2²⁵⁶-1.

Let’s define a new class constant for the Sort contract:

contract Sort {

uint constant UINT256_MAX = (2**256)-1;

The idea here is that all uint are 256 bit unsigned integers (there are other packing types for integer values such as uint16 or uint192 if you so need them) and thus the max value is 2²⁵⁶-1. Since the max value of uint is zero minus one as well, we can use that as a sanity check value.

Note: Unlike C with the limits.h library, there isn’t a native UINT256_MAX constant already available in the Solidity language, as far as I know.

Let’s review the two possible danger spots:

uint i = low - 1; // DANGER: Underflow? for (uint j = low; j < high; j++) {

if (arr[j] <= pivot) {

i++; if (i != j) {

arr[i] ^= arr[j];

arr[j] ^= arr[i];

arr[i] ^= arr[j];

}

}

}

Even though low - 1 might be underflowed, all future operations increment it (to at least 0), and it’s never used before the increment. This is fine, then.

The next section is the conditional check before recursively calling the sorting method:

if (low < pivot-1) { // DANGER: Underflow?

sort(arr, low, pivot-1);

}

Definitely a problem. If pivot is 0 then it’ll absolutely underflow and the condition will always be true.

The quick fix then is:

if (pivot-1 != UINT256_MAX && low < pivot-1) { // Avoid underflow

sort(arr, low, pivot-1);

}

Now we just need to re-run our test: 4,363,648 gas. Wait. Did we just use 17,863 more gas than the version with all the castings just because we’re having to do an additional conditional check?

What if we split up the conditional check?

if (pivot-1 != UINT256_MAX) {

if (low < pivot-1) { // DANGER: Underflow?

sort(arr, low, pivot-1);

}

}

That yields 4,361,398 gas, better, still worse than the casting version — but did you notice that splitting up the conditional actually has an effect on gas cost?

Solidity compiler: what manner of madness is this?!

Meanwhile, some of you may be screaming back at the article: just compare with 0 then, if you’re so worried about underflow!

Ah. So you see the fatal flaw now? We over-complicated the situation without a real reason. We could much more easily say:

if (pivot > 0 && low < pivot-1) {

sort(arr, low, pivot-1);

}

Gas cost with pivot > 0 instead of the UINT256_MAX check: 4,359,625.

Splitting it up again:

if (pivot > 0) {

if (low < pivot-1) {

sort(arr, low, pivot-1);

}

}

This final version costs 4,357,375 gas. This is still 11,590 gas more than the previous version using casts of uint and int .

The peril of premature optimization: we need to look elsewhere, the casts are not the efficiency problem. We can’t seem to avoid casts after all.

Reviewing the Algorithm

We’re now stepping beyond the Java -> Solidity conversion questions and into the realm of “what’s the best way to use the Quicksort algorithm for these test cases.”

The majority of this article has focused on a straightforward adaptation of an existing algorithm to Solidity from Java. We made an assumption that the original algorithm was a good Quicksort implementation.

We made an assumption. Was it true?

To review, this version uses the high value to set the pivot. What is that actually doing under the hood?

If I have a set of integers like [5, 4, 6, 1, 2] then the iteration and transformations must look like this:

low = 0, high = 4

pivot = 2

i = -1

j = 0

j is less than 4

arr[j] is 5

5 is not less than or equal to 2 (pivot)

j = 1

j is less than 4

arr[j] is 4

4 is not less than or equal to 2 (pivot)

j = 2

j is less than 4

arr[j] is 6

6 is not less than or equal to 2 (pivot)

j = 3

arr[j] is 1

1 is less than 2 (pivot)

i = 0

1 does not equal 3

swap arr[i] and arr[j]

[1, 4, 6, 5, 2]

j = 4

i+1 = 1

1 does not equal 2

swap arr[i+1] and arr[high]

[1, 2, 6, 5, 4]

pivot = 1

sort(arr, 0, 1)

sort(arr, 2, 4)

The impression I have there is than because we’re always choosing high as the pivot, we’re consistently going to have poor performance with the way it moves forward with huge second sorts and tiny, useless first sorts; it reminds me of Bubble Sort — not great average case performance.

From the Middle

We’re not the only ones who’ve ever asked ourselves how to write an efficient Quicksort in Solidity. In fact, there’s an excellent version by Subhod I on Github that uses a middle pivot for Quicksort.

Is his version faster than our pivot from the highest value? It sure looks clean.

pragma solidity ^0.4.18; contract QuickSort {



function sort(uint[] data) public pure returns(uint[]) {

quickSort(data, int(0), int(data.length - 1));

return data;

}



function quickSort(uint[] memory arr, int left, int right) internal pure {

int i = left;

int j = right;

if(i==j) return; uint pivot = arr[uint(left + (right - left) / 2)]; while (i <= j) {

while (arr[uint(i)] < pivot) i++;

while (pivot < arr[uint(j)]) j--;

if (i <= j) {

(arr[uint(i)], arr[uint(j)]) = (arr[uint(j)], arr[uint(i)]);

i++;

j--;

}

}



if (left < j)

quickSort(arr, left, j);

if (i < right)

quickSort(arr, i, right);

}

}

Wow: 1,151,833 gas!

Pivoting in the middle saved us 3,193,952 gas versus the naive Java -> Solidity implementation, even after our various optimizations!

But look, the author of the snippet above is still using the in-place value swap construct (arr[uint(i)], arr[uint(j)]) = (arr[uint(j)], arr[uint(i)]); .

We proved earlier that the XOR swap was more efficient, right?

Let’s implement our special, optimized version of Subhod’s algorithm then:

Surely this one will be the best!

Total gas cost: 1,237,550.

…

So it’s worse by 85,717 gas. Our little trick that worked so well with the previous algorithm was completely useless (and worse!) in this already well-implemented version.

Which was more important then, all of these attempts at optimizations, or checking our assumptions at the door and deciding on the right algorithm the first time?

What Have We Learned

Hopefully we’ve learned a few things:

Some basic syntax differences between Java and Solidity

Solidity method signatures and return values

Simple gotchas and danger areas with indexes and boundaries

Avoiding pre-mature optimizations, and focusing on algorithm choices first

Double-checking assumptions: XOR shift and the Geeks for Geeks algorithm looked good on the surface, until we established a test case to compare against

The Solidity compiler handles conditional checks differently than you might have expected (lower gas cost by nesting conditions)

Avoiding casts isn’t always necessary

Method calls have a measurable cost

Over-complicated conditions (2²⁵⁶-1 instead of > 0) have a measurable cost

Just remember, look for established patterns first so you have benchmarks to compare against. And good luck golfing!