327

Apart from the public modifier Ethereum introduces the external one. Both may be called outside of the contract and inside (the later one by this.f() pattern). Moreover, according to the docs:

External functions are sometimes more efficient when they receive large arrays of data.

but there is no further information what does actually sometimes mean and if this efficiency gain is held also for internal calls.

What are the best practices of using external vs public keyword? Are there any patterns or recommendations?

1
  • "Both may be called outside of the contract and inside"? Both, or only public ? Commented Jan 25, 2023 at 12:19

9 Answers 9

560

A simple example demonstrating this effect looks like this:

pragma solidity^0.4.12;

contract Test {
    function test(uint[20] a) public returns (uint){
         return a[10]*2;
    }

    function test2(uint[20] a) external returns (uint){
         return a[10]*2;
    }
}

Calling each function, we can see that the public function uses 496 gas, while the external function uses only 261.

The difference is because in public functions, Solidity immediately copies array arguments to memory, while external functions can read directly from calldata. Memory allocation is expensive, whereas reading from calldata is cheap.

The reason that public functions need to write all of the arguments to memory is that public functions may be called internally, which is actually an entirely different process than external calls. Internal calls are executed via jumps in the code, and array arguments are passed internally by pointers to memory. Thus, when the compiler generates the code for an internal function, that function expects its arguments to be located in memory.

For external functions, the compiler doesn't need to allow internal calls, and so it allows arguments to be read directly from calldata, saving the copying step.

As for best practices, you should use external if you expect that the function will only ever be called externally, and use public if you need to call the function internally. It almost never makes sense to use the this.f() pattern, as this requires a real CALL to be executed, which is expensive. Also, passing arrays via this method would be far more expensive than passing them internally.

You will essentially see performance benefits with external any time you are only calling a function externally, and passing in a lot of calldata (eg, large arrays).

Examples to differentiate:

public - all can access

external - Cannot be accessed internally, only externally

internal - only this contract and contracts deriving from it can access

private - can be accessed only from this contract

15
  • 56
    Excellent and a very helpful answer. Thx Tjaden! Commented Jul 4, 2017 at 13:48
  • 8
    If we are designing a public interface for contracts and other people will rely on the interface (think EIPs), then should we always be using external? Commented Jan 20, 2018 at 5:56
  • 8
    ^ This should be added to the Solidity docs.
    – Justin
    Commented May 15, 2018 at 16:53
  • 2
    Something wrong with your answer: joxi.ru/Drlz51Xc4MDGX2 Commented May 16, 2018 at 14:13
  • 6
    Solidity 0.6.9 now allows calldata to be used for any variable or parameter, even in internal functions. Commented Jun 9, 2020 at 5:32
55

Update for Solidity ^0.8

Tjaden's answer is great, but I think that it deserves an update for the latest versions of Solidity. His code snippet doesn't compile anymore. You get this error now:

Data location must be memory or calldata for parameter in function, but none was given.

That is because of the new requirement to be explicit when using reference types, such as arrays. Also memory and calldata are now allowed in all functions regardless of their visibility.

A rewrite would look something like this:

pragma solidity >=0.8.13;

contract ExternalPublicTest {
    function foo(uint[20] memory a) public pure returns (uint){
         return a[10]*2;
    }

    function bar(uint[20] calldata a) external pure returns (uint){
         return a[10]*2;
    }    
}

As a side note, you can decode calldata variables into memory but not the other way around.

25

Restructuring the answer above for clarity:

pragma solidity^0.4.12;

contract Test {

    /*
    Cost: 496 Gas 
    This can be called internally or externally
    Since internal calls expects function arguements to be allocated to memory, solidity immediately
    copies array arguments to memory (This is what cost the additional gas.) 
    */
    function test(uint[20] a) public returns (uint) {
        return a[10] * 2;
    }

    /*
    Cost: Gas 261
    Doesnt allow internal calls, read directly from CALLDATA saving on the copying step(memory allocation).
    */
    function test(uint[20] a) external returns (uint) {
        return a[10] * 2;
    }


    /*
     Executed via JUMPs in code, array arguments are passed internally by pointers to memmory
      Function expects argument to be located in memory. 
     */
    function test(uint[20] a) internal returns (uint) {
        return a[10] * 2;
    }
}
  • Internal calls are the cheapest, since its executed via code JUMP, passing pointers to memory.
  • Internal calls for public functions are expensive because internal function calls expects the arguments to be allocated to memory, since the public function does not know if the invocation is external or internal, it copies the arguments to memory and hence is more expensive
  • If you know the function is only going to be called externally use external

  • It almost never makes sense to use the this.f() pattern, as this requires a real CALL to be executed, which is expensive.

18

Just checked the result with the latest compiler. It seems that the gas cost reduction is caused by memory vs calldata, i.e., whether a function is external or public does not matter. What matters is whether the input array is memory or calldata.

// SPDX-License-Identifier: MIT

pragma solidity 0.8.4;

contract ExternalPublicTest {
    function test(uint[20] memory a) public pure returns (uint){
         return a[10]*2;
    }

    function test2(uint[20] calldata a) public pure returns (uint){
         return a[10]*2;
    }    
}
9

Simple answer

The public is equivalent to external plus internal.

In other words, both public and external can be called from outside your contract (like MetaMask), but of these two only public can be called from other functions inside your contract.

Because public grants more access than external, the general best practice is to prefer external. Then you can consider switching to public if you full understand the security and design implications.

1
  • Other functions can call both your public and external functions inside your contract, but when calling external functions they must use message-calls as this.func Source : docs.soliditylang.org/en/latest/…
    – James
    Commented Nov 20, 2022 at 5:20
4

I have tried with solidity 0.8.14

pragma solidity 0.8.14;
contract Test {
   function test(uint[2] calldata a) external pure returns (uint){
     return a[1]*2;
   }
}

as you can see the function visibility specifier used is external

pragma solidity 0.8.14;
contract Test {
   function test(uint[2] calldata a) public pure returns (uint){
     return a[1]*2;
   }
}

in this case the function visibility specifier used is public

for both cases the gas consumed is 22116 (in remix) so there is no difference between external and public in terms of gas consumption

Another test:

pragma solidity 0.8.14;
contract Test1 {
  uint a;
  function test() external{
       a = 1;
  }
}

In this case I have tested a transaction that change the contract state of the contract and the execution cost is 43300

pragma solidity 0.8.14;
contract Test2 {
  uint a;    
  function test() public{
       a = 1;
  }
}

executing the same function but changing from external to public I got the same result: execution cost equals to 43300

So in terms of gas consumption I didn't see any difference

4

2023 Answer

The accepted answer used to be correct but now it's outdated. Check this up to date answer:

does using external over public in a library reduce any gas costs?

3

Also note that external means external to the contract, not the network. Both external and public functions may be called from another contract within same transaction. From the doc:

External functions are part of the contract interface, which means they can be called from other contracts and via transactions.

So external does not prevent reentrant calls to the function. For that, one can use OpenZeppelin's ReentrancyGuard, but that costs gas.

1

So, for the latest compilers, an external function is a public function that forces its arguments to reside in calldata, while a public function is a function that is visible from outside and allows its arguments to reside both in memory and calldata.

2
  • 1
    For external calls the data always reside in the calldata even when the function is public. In that case there will be a conversion step from calldata to memory.
    – Ismael
    Commented Jan 2, 2022 at 4:41
  • There is also no point to specifying calldata in a function that makes additional calls to internal functions-unless you want to enforce read-only linting-as it will be invariably copied to memory before being passed to the internal function. Commented Jan 20, 2022 at 4:05

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.