By the end of this article, we’ll face a very powerful and mildly confusing concept of function types.
We’ll need a bit of our legendary focus and imagination to get through it, but after that, we’ll have an incredibly powerful tool in our toolbox!
It took your author three rounds of reading the original documentation to make all the pieces fit together, but I’m sure you’ll grasp everything from this article on the first try.

π¬ Why I’m saying this is: don’t ever get discouraged if something seems too abstract or convoluted. When needed, give yourself enough time and opportunity to understand the material, and take enough rest in the meantime. In my experience, and I do have some, I always get the right idea or a solution to a problem when I’m not looking at it, and often even when I’m not actively thinking about it. I guess that’s when the unrestrained imagination takes off and finds its own way.
It’s part of our long-standing tradition to make this (and other) articles a faithful companion, or a supplement to the official Solidity documentation, for this article’s topics.
PDF Download: You can download or recap the slides associated with this video below just before the “Conclusion” section. π
Function Types
It’s not common for our articles to start with philosophical considerations, but it’s exactly what we’ll do: we’ll introduce a fundamental definition of a type.
It is a necessary step for us to later analyze and understand the term function type as it’s defined in the original Solidity documentation. After that, we can shift to the top gear and power through the rest of the article.
π Info: “(attributive) a: A type is a particular kind, class, or group.” (Merriam Webster).
Therefore, when we’re discussing a type of an entity of our interest, we’re taking into account its properties, declaration, formal description, etc.

According to the original Solidity documentation (and I found it very adventurous, if not recursive) is:
“Function types are the types of functions. Variables of function type can be assigned from functions and function parameters of function type can be used to pass functions to and return functions from function calls.”
Before diving in, let’s see how function types are stated:
function (<parameter types>) { internal|external } [pure|view|payable] [returns (<return types>)]
With this in mind, we are starting to develop a notion of what a function type is: function type is a set of function properties, i.e. parameter and return types, function visibility, and state mutability.
A function type is given by the function header declaration.
Ok, let’s sober up and dismantle the definition above sentence by sentence, using our growing example as a scaffold.
“Function types are the types of functions”. In light of what we’ve learned, we know that functions are determined by their function types.
Let’s take a look at how we’d create a new function type func
:
function func()
This function type is extremely simple and equally useless, but it’s a function type. So far, so good.
Let’s try our luck with the next sentence: “Variables of function type can be assigned from functions…”.
This part of the sentence says that we can receive another function via our function parameter, e.g.
// SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.8; contract functionTypeDemo {
A function for doubling the number.
function doubleIt (uint aNumber) internal pure returns (uint anotherNumber) { anotherNumber = aNumber * 2; }
A function for squaring the number.
function squareIt (uint aNumber) internal pure returns (uint anotherNumber) { anotherNumber = aNumber ** 2; }
Applies a function over a parameter, based on the function selector.
function func(uint aParameter, uint8 funcSelector) public pure returns (uint aReturnParameter) {
Declares a function type variable.
function (uint) pure returns (uint) f;
Assigns a function to a variable.
if (funcSelector == 1) { f = doubleIt; } else { f = squareIt; }
A referenced function call.
aReturnParameter = f(aParameter); } }
Then this part of the sentence says: “…and function parameters of function type can be used to pass functions to and return functions from function calls”.
I’m far from being formal with this description, but for all practical uses, it’ll serve us well.
Function Call
A function call is, in simple terms, a process of entering the function body and executing it. When we declare a function type with parameter types, we can pass the calling function arguments via the function call to the parameters of a called function.
Function type parameters can be parameter types and return types:
contract functionTypeDemo2 {
A function for doubling the number.
function doubleIt (uint aNumber) internal pure returns (uint anotherNumber) { anotherNumber = aNumber * 2; }
A function for squaring the number.
function squareIt (uint aNumber) internal pure returns (uint anotherNumber) { anotherNumber = aNumber ** 2; }
The function applies the function (reference) received via its parameter.
function func( uint aParameter, function (uint) pure returns (uint) f ) internal pure returns (uint aReturnParameter) { aReturnParameter = f(aParameter); }
The function is passed as an argument depending on the selected option.
function funcSelected(uint number, uint8 funcSelector) public pure returns (uint result) { if (funcSelector == 1) { return func(number, doubleIt); } else { return func(number, squareIt); } } }
Internal and External Functions
Function types are available in two variants: internal and external functions.
Internal functions are only available for function calls originating from the current contract, i.e. the current code unit including internal library functions and inherited functions. Internal functions cannot be called from outside of the current contract’s context.
External function calls can only be applied to external functions, which are targeted by an address and a function signature.
As we have noticed in the function type syntax above, the parameter types can be omitted. This is not the case with the return types; the return
keyword is optional, but if we do use it, return types are necessary.
Otherwise, if the function doesn’t return a result, the return keyword must be omitted.
Default function type visibility is internal (when omitted), in contrast to the function visibility that has to be explicitly defined.
Function Types Conversions
Implicit conversion from function type A to function type B happens under very strict and precise conditions:
- Their parameter types are identical;
- Their return types are identical;
- Their internal/external function visibility property is identical;
- The state mutability of A is more restrictive than the state mutability of B.
Even more specific:
pure
functions can be converted to view and non-payable functions;view
functions can be converted to non-payable functions;payable
functions can be converted to non-payable functions.
Here’s a clarification on the last rule, about payable and non-payable conversion:
π‘ “The rule about payable and non-payable might be a little confusing, but in essence, if a function is payable, this means that it also accepts payment of zero Ether, so it also is non-payable. On the other hand, a non-payable function will reject Ether sent to it, so non-payable functions cannot be converted to payable functions.”
(https://docs.soliditylang.org/en/v0.8.15/types.html#function-types).
Besides the listed conversions, no other conversions are possible.
Calling a non-initialized function type variable or a function type variable after using delete on it will result in a Panic error.
When external function types are used outside of the context of Solidity, they are treated as the function type. This means that the address followed by the function identifier is encoded together in a single bytes24
type.
We should remember that public function visibility acts like internal (visible only to the contract and the inherited contracts) and external function visibility (visible only to external contracts) are applied simultaneously. Therefore, to use a function f as an internal function, we’d just dereference it as f. Otherwise, to use it in the external form, we’d dereference it as this.f.
We can assign an internal function to a variable of an internal function type, regardless of the location where the variable is defined. Locations include private, internal and public functions of both contracts (the one hosting the internal function and the other one hosting the internal function type variable), libraries, and free functions (the ones defined outside the contract).
In contrast, we can assign only external and public functions to an external function type variable.
Libraries as locations are excluded, too, because they require a delegatecall
and use a different ABI convention for their selectors (docs).
Functions declared in interfaces do not have definitions so pointing at them also wouldn’t work.
Members of External and Public Functions
External and public functions have these two essential members (properties):
.address
returns the address of the contract of the function..selector
returns the ABI function selector
Info: The ABI function selector works in the following way: π
“The first four bytes of the call data for a function call specifies the function to be called. It is the first (left, high-order in big-endian) four bytes of the Keccak-256 hash of the signature of the function. The signature is defined as the canonical expression of the basic prototype without a data location specifier (memory, storage, calldata), i.e. the function name with the parenthesized list of parameter types. Parameter types are split by a single comma – no spaces are used.”
(https://docs.soliditylang.org/en/v0.8.15/abi-spec.html#function-selector).
π‘ Note: To specify the amount of gas or the amount of Wei sent to a function, we should use {gas: ...}
and {value: ...}
.
π¦ Warning: We must use the
modifier with the function on which we’re using the value option. Otherwise, the value option would not be available.payable
Let’s take a look at how we can use external function members:
// SPDX-License-Identifier: GPL-3.0 pragma solidity >=0.6.4 <0.9.0; contract Example { function f() public payable returns (bytes4) {
Asserts that the function-holding contract address is the same as this contract’s address.
assert(this.f.address == address(this));
Returns the ABI function selector.
return this.f.selector; } function g() public {
The function is called with gas and Wei value defined.
this.f{gas: 10, value: 800}(); } }
Tutorial Slides as PDF
You can download or recap the slides associated with this video here: π
Conclusion
In this article, we started with a somewhat tricky, but extremely useful subject of function types.
First, we considered what a type is, then we analyzed the original Solidity documentation sentence by sentence and looked at several examples to understand the meaning behind the not-so-straightforward definition.
Second, we greased our hands while fiddling with the mechanics of function types conversions.
Third, we focused on the members of the most exposed, celebrity functions, those declared with external and public visibility.
What’s Next?
This tutorial is part of our extended Solidity documentation with videos and more accessible examples and explanations. You can navigate the series here (all links open in a new tab):