There are a few reasons why you might want to initialize a contract after deployment and not directly by passing constructor arguments. But first let's look at an example:

The first snippet deliberately omits the guard to introduce the pattern. It accepts repeat calls from anyone. The later flag is still schematic and must be combined with the deployment and authorization rules below.

solidity
contract MyCrowdsale {
  uint256 rate;

  function initialize(uint256 _rate) public {
    rate = _rate;
  }
}

What's the advantage over constructor(uint256 _rate)?

  • Deployment and configuration does not need to happen at the same time. This can be useful when your workflow requires it.
  • Easier Etherscan verification as you do not have to deal with messy constructor arguments.
  • Can help reorganize configuration when a particular function produces a stack-too-deep compiler error; there is no universal 13-argument cutoff.
  • Is used by proxy-based upgradeable contracts, where the proxy needs its own initialized storage. An initializer alone does not make a contract upgradeable.

Avoid multiple initializations

Make sure that you do not allow multiple initializations. A parameter check such as require(rate == 0) is insufficient when zero is a valid configured value. Use an explicit initialization flag, or the OpenZeppelin Initializable pattern. The first call must also be controlled: initialize through the deployment transaction or apply suitable authorization. For proxies, initialize the proxy during deployment and lock the implementation with _disableInitializers(). Here is a schematic one-time flag:

solidity
contract MyContract {
  bool isInitialized = false;

  function initialize(
    uint256 _param1,
    uint256 _param2,  
    uint256 _param3,
    address _param4,
    address _param5,
    bytes32 _param6,
    bytes32 _param7
  ) public {
    require(!isInitialized, 'Contract is already initialized!');
    isInitialized = true;

    param1 = _param1;
    ...
    param7 = _param7;
  }
}