Solidity Basics Part 1: Variables – Everything You Need to Know

Want to give your Solidity learning a head start? Then learn the basics, starting with variables. Here's a comprehensive article that will teach you EVERYTHING about them.

Solidity Basics Part 1: Variables – Everything You Need to Know

So, you’re interested in learning Solidity but the official docs and the blogs out there aren’t too friendly. The information is either too dense or complicated to grasp at first. You need an alternative.

Here, we’re going over the most important parts regarding variables. From how to use them, where they’re useful, and even the best practices on how to write them – written in a friendly and understandable way for everyone.

The hope is to explain Solidity’s main concepts in a way that either technical or non-technical people can understand. Give your Solidity learning a head-start with the info below 😊

What is Solidity?

EASY – Solidity is Ethereum’s programming language. It follows ECMAScript syntax so you may feel comfortable with Solidity if you’ve programmed in JavaScript, C++, or Python before. If you haven’t, don’t worry – it’s not that hard.

You can use Solidity to program smart contracts, like the ones behind ERC20, ERC721, ERC777, and ERC1155 tokens.

In other words, Solidity lets you create smart contracts for crypto coins, NFTs, and entire transaction systems that run on the Ethereum blockchain.

What are Smart Contracts?

You can say a smart contract is a piece of executable code that controls transactions between parties or systems.

In Ethereum, every smart contract runs on the EVM (Ethereum Virtual Machine). It is where wallets and smart contracts live. It is also where every transaction gets recorded through prosperity.

You can write smart contracts for voting mechanisms, the behavior of fungible (ERC20) or non-fungible tokens (NFTs), swapping coins, and a lot more…

The whole purpose of a smart contract is to reduce human input as much as possible by automating transactions done with ETH or data saved within the Ethereum blockchain.

This helps minimize fraud, costs, and accidents.

What is This Guide About?

Solidity consists of various data types that make the basics of how a specific token or contract works. The most common type of data is a variable.

· Variables – hold simple data like booleans, addresses, strings, integers, and bytes

· Variable Modifiers – tell functions how to interact with other data types, functions, and contracts

· Environment variables – blockchain variables that return things like transaction gas and block hashes

Below, we go a bit deeper into what variables are and how you can use them. But first, let’s teach you how to test the code snippets.

How to Test Solidity Code in This Guide?

The default way to test your smart contracts is by writing them in Remix. This is a browser IDE and compiler that lets you play around with Solidity code before you even try to deploy to the mainnet or a testnet.

Click here to go to the Remix IDE (directs to

Here’s what you need to know about Remix at first

Code Editor

Here’s where you’ll be writing most of the Solidity code. Every snippet you’ll see below you’ll paste in here.

Check how it’s formatted (you’ll replace everything except the SPDX license as you paste some of the examples I’ll give you).


You will notice all the transactions happening in this section. It will also show you where there’s an error and so on.

Given all the snippets below are written to be correct, you won’t notice many errors happening unless you start to experiment.

File Explorer

We aren’t going to use this one much. It contains the .sol files we’re writing. If you need to create or modify contracts, this is the place where you’re going to do that.


This is where you go after writing and saving a contract successfully. You click on the Compile ContractName.sol to make the contract available later on.

If there’s an error in your code for one reason or another, the compiler will show you below the buttons in this way:


This is where you go after the contract is successfully compiled. You will need to click the `Deploy` button if you want the contract to be available below.

Once you click Deploy, you will see a small line saying `Contract at 0x…` appearing below. You click on that line and it opens up the public contract functions and variables. This part lets you interact with the contract directly.

With all that in mind, you can start experimenting with Remix yourself. However, you may not have any idea what anything means yet (so take a look below).

Solidity Variables – Types and Examples

With a basic idea of what Solidity and smart contracts are, let’s learn about variables.

These are the parts of smart contracts that DEFINE the different types of data you’re using. If you come from other programming languages, you probably already know the types of variables (booleans, strings, integers, etc.).

Types of Variables

In Solidity, however, there are two types of variables – state and local.

state – you declare them inside contracts and outside functions. They can be called anywhere inside functions but only if they’re first declared outside.

local – these go inside functions. You can’t use them outside of the function where it’s called. Once a function call happens, local variables disappear.

Examples of both:

contract VariableExample {

uint public stateVariable;

    function returnVariable() public {
        uint variable = 10;
        stateVariable = variable;

Copy-paste the code in Remix. Then deploy. You will get the returnVariable() function and the stateVariable variable.

Click on stateVariable and see that it’s 0. Then click on returnVariable and check `stateVariable` again. You’ll see that it changes.

The uint stateVariable is declared outside the function but you can use it inside to change its value. The uint variable, however, is only called inside the function to help change the value of stateVariable. In that case, the uint variable can’t be used outside that function.

NAMING CONVENTION: You need to use mixedCase for variable names. Whether they’re boolean, uint/int, string, or addresses.


These variables are either TRUE (1) or FALSE (0). If you want a function to only happen after a person has paid a fee, you can create a boolean variable that changes once the person has paid the fee. This will allow the function to happen. Otherwise, if the boolean is negative, then it won’t happen. That’s the most common use of booleans.

Here’s an example:

contract BooleanExample {
	bool understood = false;
    function setUnderstood() public {
		understood = true;
    function isItUnderstood() public returns (string response) {
		if (understood) {
		response = 'yes';
		} else {
		response = 'no';

As you can see above, you’re declaring the boolean understood at first. It is set to false. Then you have the response variables. It is empty. If you click on the response button, it will say 0.

Once here, you can click the isItUnderstood() function. Then click on the response variable. You should see a string saying no.

After that, you can then click on the setUnderstoodTrue function. You’ll see how a transaction happens in the console. Click isItUnderstood() once again and the response variable should change to yes. Try it out.

Once the understood boolean is changed, you can use it with require() on other functions to perform different actions depending on whether the boolean is true or false (more on that later). In this case, you can see how understood is used as a parameter to change response.


There are two types of integers. One is called signed and the other unsigned. They’re used for pretty much the same. However, one can return negative values, the other can’t.

int – These are signed integers (numbers) that can go from -1000000 to 1000000. You can write negative (-) signs to these integers.

uint – These are integers (numbers) that only go from 0 to 1000000 - they're unsigned, so there's no way to write a negative (-) number with a uint.

You will find the number of bits on the side (they should be 8 or multiples like 16, 24, 32, 40, 48, 56, 64, etc.). This number can be as little as int8 / uint8 and as high as int256 / uint256.

The logic is that every integer bit you mention is elevated to 2. That is, an uint8, for example, will have a maximum limit of 256 (the result of 2^8), so you can’t store any number above 256 on an uint8.

For an uint64, on the other hand, you can store numbers up to 18,446,744,073,709,551,616.

You can also write without adding the number of bits like `uint` or `int`, which Solidity automatically sets as 256 bits.

WHY ARE NUMBERS AFTER THE UINT/INT IMPORTANT? Well, every transaction on Ethereum costs money depending on the size of the variables being used. A smaller variable like uint8 will need less space, thus consuming less gas per transaction than an uint256.

Here’s an example on how to use them:

 contract ExampleIntegers {
	uint8 public uint8Example = 100;
	int8 public int8Example = -100;
	uint public uintExample = 1000000;
	function changeUint8(uint8 newNumber) public {
		uint8Example = newNumber;
    function changeInt8(int8 newNumber) public {
		int8Example = newNumber;
    function changeInt8 (uint newNumber) public {
		uintExample = newNumber;

In this case, the uint8Example, int8Example, and uintExample will need to have a number that fits within the number of bits they can handle.

You’re declaring them above, but you aren’t setting up a number – so the numbers coming to the function need to be either of those two (or it will overflow and cause an error).

You can test the code above by deploying and using the changeUint8, ` changeInt8`, and ` changeInt8` functions to set new numbers. You’ll see an input alongside the button, that’s where you’re writing the value you want to change the integers to. After calling the function, you can call the variables again to see what they changed too (the same number you entered, accordingly).

You’ll see how trying to set either uint8 or int8 to more than 255 will show an error console. Similarly, trying to set the `uint` to a negative value will also error.

NOTE: uint can be used as arrays via uint[] where you can store several numbers at once.


Strings in Solidity are UTF-8. That means, they’re a text chain. You can say they basically save characters.

You can add numbers and words to strings. They’re a type of data, but Solidity also treats them as arrays.

To use string literals, you need to initialize them within quotation marks.

Here’s an example of how to declare them and use them:

contract StringExample {
	string public exampleString;
	string public initializedString = 'this is 1 string';
    function changeExample(string memory changeExample) public {
		exampleString = changeExample;

	function changeInitialized(string memory changeInitialized) public {
		initializedString = changeInitialized;

At first, you can test the exampleString and initializedString. You’ll see exampleString being 0 or null. Then you see the initializedString with this is 1 string.

The changeExample and `changeInitialized` are functions that receive strings with the same names. These strings can be pretty much any character.

Call the functions by writing any string you want within the input. Then check exampleString and initializedString again to see what they changed to.

NOTE: strings can be used as arrays as string[] when you want to store several strings in one.


These hold bytes. Solidity lets you use bytes1, bytes2, bytes3, and up to bytes32. However, it’s recommended to just use bytes if you want to save gas (as it reduces the number of bytes to the minimum possible).

Here’s an example:

contract ExampleBytes {
    bytes4 public exampleBytes = "yes";
    bytes32 public exampleBytes2 = "yes";
    bytes public exampleBytes3 = “yes”;

Here, when you call bytes4 exampleBytes on “yes”, you will get that bytes4 is equal to 0x79657300 (the first four bytes).

If you set it up bytes32 exampleBytes2with the same word “yes”, you will get 32 bytes instead, 0x7965730000000000000000000000000000000000000000000000000000000000.

Lastly, using bytes exampleBytes3 alone return you only 0x796573. That’s because it reduces the number to the minimum possible bytes without any useless zeroes.

Bytes are typically used to transform strings (not a variable but a name or number) into bytes.

NOTE: Bytes can be used as arrays via bytes[] where you can store several bytes at once.


This is self-explanatory. Every Ethereum address that interacts with a contract can be considered an address variable. This could be either the owner of a contract, the sender or receiver of a transaction, on an array of addresses, and so on.

contract ExampleAddress {
    address public firstAddress;
    address public secondAddress = 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4;
    address public thirdAddress = msg.sender;

Here, you can use the firstAddress as an empty variable to save any address that interacts with the contract.

Then you have the secondAddress, where you’re already saving an address. Every time you call secondAddress, you will use exactly the address you selected (this address is modifiable, by the way).

And lastly, you have the third address. This one returns the msg.sender which is an environmental variable (more on that later). The `msg.sender` refers to the person who sends the transaction (whoever is interacting with the contract at the moment).

Variable Modifiers – Visibility and Usage

Modifiers can tell variables how to behave (what they do) and where they’re available (whether you can call them from inside the contract).

There are various modifiers for variables to consider:


These are mostly used on state variables. A constant, as the name says, is a variable that can’t be modified. Once you declare a variable as constant and assign a value, it will hold that value throughout the contract and in every function it’s called. If you try to change its data, Solidity will throw an error.

Example of constant variable in Solidity:

contract ConstantExample {
	uint public constant NUMBER_GO_UP = 10000;

You won’t do much with this code on Remix. However, you can try to write a function like this:

function changeConstant() public {
    NUMBER_GO_UP = 100;

If you copy/paste this function and try to deploy the contract again, you’ll get an error that says “Cannot assign to a constant”.

The purpose of a constant is to be available for other functions while holding an unchangeable value. Say you want to limit the amount of ETH sent in a transaction or how many NFTs a person can mint – then you can use a constant like this to set a state variable that holds exactly that limit.

NAMING CONVENTION: You need to write constants in ALL CAPS. If there are many words in the variable, you write underscores “_” to separate the words. Example: THIS_IS_A_CONSTANT.


These are also like constant variables, that you can’t modify once the contract has been compiled. If you try to change them within functions, you’ll get an error.

However, you can create empty state variables at first and then assign a value INSIDE constructors. (A constructor is called on contract creation).

You can set immutable variables on contract creation, just like constant if you want.

Here’s an example:

 contract ImmutableExample {
    uint public immutable firstNumber = 1;
    uint public immutable secondNumber;

    constructor(uint number) {
    	secondNumber = number;

As you can see, the first string firstNumber is set to 1. Once it’s declared, you can’t change it.

The secondNumber variable, however, is not assigned. But you call it in the constructor and assign it whatever number you want on deployment.

You’ll notice the Deploy button on Remix now has an input on the side. You write a number on that input and deploy the contract.

Once deployed, you can click on the variables firstNumber and secondNumber to see how they’ve changed.

NAMING CONVENTION: Immutable variables can be called as mixedCase or lower case.


The public modifier is added to a variable whenever you want to make it available for everyone to see and use. There’s not much more to public variables than that.

Here’s how you can see the difference between a public and a non-public variable:

 contract PublicExample {
    string public availableVariable;
    string nonAvailableVariable;

After deploying the contract in Remix, check the value of availableVariable and nonAvailableVariable. You’ll notice it’s not even possible to read the value of the second string.


In contrast with public variables, private ones allow only the contract to use it. You can’t read nor use it from any function outside this contract.

contract PrivateExample {
    string public publicVariable;
    string private privateVariable;

Like above, you can’t even see the privateVariable to see its values. However, you can use it in any function in the contract without any problem.


In contrast with external variables, you can only call internal ones within functions in the same contract. A derived contract can also call `internal` variables. But unrelated contracts cannot read or change `internal` variables at all.

Example of internal variables and their use:

contract ExampleInternal {
    uint256 internal _internalvariable = 10;

You can use this one within functions on the same contract. And if a derived contract calls it, you will get its result.

To test that, go to the File Explorer in Remix and create a new contract. Remember to add the SPDX and Solidity version on top. Then add this:

import ExampleInternal.sol';

contract CallInternalVariable is ExampleInternal {

    function callInternal () public view returns (uint256) {
 	   return _internalvariable;

Once you deploy both contracts, you can see in the new contract the function callInternal(). As you call it, you should see the value of your _internalvariable as if it were this same contract.

If you try to do the same with _internalvariable from an outside contract, you won’t be able to.

NAMING CONVENTION: Internal variables are typically declared with an underscore as a prefix and the digits in lower case. This prevents confusion of internal variables with other variables. Example: _internalvariable

Environment Variables – Types and Examples

Also known as blockchain variables, these refer to transaction properties from the Ethereum blockchain and aren’t modifiable within your smart contracts. That is, you can use Solidity to call them from the blockchain and use them if necessary, but you can’t change their values.

These environment variables include:


Returns the address of the miner who's processing the block. This is an address payable by default. Here’s an example:

contract Example {
    address miner = block.coinbase;
    address public showMiner = miner;

This should return an address once you click the showMiner. The address will be the transaction’s hash miner.


It returns the difficulty of the block. This is typically shown as a 14-digit number. Every `block.difficulty` is an `uint256` by default.

Here’s how to show it:

contract Example {
	uint256 public difficulty = block.difficulty;

After calling difficulty in Remix, you will see a 14-digit number below. You can test with various uint256 integers by changing the difficulty by any other name. Call the integer again and see how it changes to a different 14-digit number.


It returns the gas limit from the block so you don't go over the limit (and spend more money than necessary)


contract ExampleGasLimit {
	uint256 public gasLimit = block.gaslimit;

Deploy and click the gasLimit button. You should see a 160000000 number at first. This is often changeable via Metamask at the moment of the transaction but not within your contract.


As simple as just returning the block number. This block number will be related to the number of blocks deployed in the chain, often taking very big large numbers if you’re deploying to mainnet, and taking small numbers on testnets (like in Remix).

Here’s an example:

contract ExampleBlockNumber {
	uint256 public blockNumber = block.number;

When you click on the blockNumber via Remix, you get 0 at first. You delete the contract and deploy once again. After clicking blockNumber a second time, the number goes to 1. If you repeat, you’ll get 2 instead.

This happens because block.number always increases by one (1). If you deploy to mainnet instead of using Remix, you’ll notice something like 85384939. If you get block.number again after the first deployment, you will get 85384940 instead.


This will return the time (in seconds) of the current block.

contract TimestampExample {

    uint public blockTime;
  	function changeBlockTime() public {
   		blockTime = block.timestamp;

As you deploy and use this contract, you’ll notice how the blockTime returns 0 when you first click it. Then, after performing the changeBlockTime(), you get a multiple-digit number. It refers to the seconds on the block. If you click it again, you’ll see how the number increases every time you activate the function.

BE AWARE: A block.timestamp has many uses, but some of them can make your contract vulnerable so it’s always essential to do it carefully (or avoid it). As a general rule avoid using a block.timestamp for random numbers (as miners can interact with the value and trick the contract).

It returns data in the transaction in bytes4 form. The data is typically the payload sent through a function or method. As you call a function, this `` sends the information for Solidity to perform such function.

Example of using

contract MsgDataExample {

    bytes public data;
    function changeData() public {
   	 data =;

This is not a common variable to use but could be useful to understand how Solidity works. When you perform the contract function above and click on bytes at first, you’ll get 0. Once you click changeData() and back to data again, you’ll notice how it changes to an 8-digit number. This is the data in bytes4 format.


This is the gas that remains after a transaction. You can use this variable to know how much gas is left before trying to perform or after calling a function. If there’s not enough gas left after calling any function, you can use gasleft() to prevent any other function from being called and waste gas unnecessarily.

The gasleft() automatically returns an uint256 so you need to read it as such. Here’s an example of how it works:

contract MsgGasExample {

    uint public gas = gasleft();
    uint public gasUsed;
    function wasteGas() public {
        uint256 _number = 10000;
        uint256 startGas = gasleft();
        if(_number > 1) {
        gasUsed = startGas - gasleft();
        	gas = gasleft();

In this example, you will need to call the gas variable at first. You will get an 8-digit number in Remix.

Then, you will click on the wasteGas() function. After the function is called correctly, click `gasUsed` to see how much gas you’ve consumed.

Finally, click the gas variable again and see how much gas is left now. You should see how the `gas` number is now lower than before.

The amount of gas will change depending on what’s happening within a certain function. For example, a function that changes data within a variable in storage will be more expensive than a function that changes a variable in memory.


This is one of the simplest environment variables. It pretty much refers to the address connecting with the contract at the moment. If an address calls a function within that contract, then that wallet automatically becomes the msg.sender.

You can use it for pretty much anything on call. From receiving and sending ETH to modifying access to the contract, and much more.

The variable msg.sender will return the address whenever you call it (as it refers to the address of the person interacting with the contract).

Here’s an example of how to use msg.sender in Solidity:

contract MsgSenderExample {
    address public contractOwner;
    function changeOwner() public {
    	contractOwner = msg.sender;

In this example above, you’re setting the contractOwner to be the person who interacts with the contract. After setting the contractOwner and using require() or setting up modifiers (more on another article), you can prevent addresses that aren’t contract owners to interact with certain parts of the contract.

By the way, remember that this variable gives away an address. It doesn’t have to be a personal wallet, by the way. If a contract is calling a function, then the msg.sender will be the address of the contract that’s interacting with such a function.

This is how Ethereum records what interacts with a contract and makes sure the address gets stored in the transaction log.


It returns a bytes4 from the transaction data (function calls mostly). The bytes string is typically a 10-digit number including the 0x from a typical address.

Here’s how to get the msg.sig:

contract MsgSigExample {

    bytes4 public shortSig;
    bytes8 public longSig;
    function asignSigs() public {
        newSig = msg.sig;
        longSig = msg.sig;

After deploying the contract above, you can click on the shortSig and longSig and check they are both zero. Then you can call the asignSigs() function.

Once you activate the variables again, you’ll notice how the `longSig` contains the same numbers as shortSig but with the difference that you get a few extra zeros. This happens because msg.sig is a bytes4 (10-digit number) by default. So it’s not possible to make it a longer number.

Most people use msg.sig for error handling. However, it may also work as a way to interact with contracts and see how different functions send different data to the blockchain.


It will show the amount of WEI that you’re sending in a transaction. Be aware that WEI is the minimum unit you can use to count ETHER. For example, 1 ETH is 1,000,000,000,000,000,000 WEI.

This value will let you check how much WEI there’s in a transaction before or after it happens.

Here’s how you can use `msg.value` in Solidity

contract MsgValueExample {
    uint public value;
    function callValue() public payable {
    	value = msg.value;

In the code snippet above, you get the chance to call callValue. You’ll notice that value is set to 0 by default, even after you call the function. However, you can always add some WEI in the Remix Deployer section where it says VALUE. Then you can check whether you want to send WEI, GWEI, FINNEY, or ETH.

Either way, you will only see WEI. To see the amount of WEI you sent, you can call the variable again. It should be set to the amount of WEI you used in the deployer to call the `callValue` function.

BE AWARE: You can only use msg.value within a payable function like the one above. Payable functions (more on them later), are the ones that let you assign ETH to transactions or messages.


It tells you how much gas is worth in the transaction. Typically, this varies depending on how much people are using a certain ETH net. In Remix, the default will be 1 WEI per gasprice, as that’s the typical amount you’ll consume per transaction.

This tx.gasprice gives you an uint. The amount you get will be in WEI, the minimum denomination of ETH.

Here’s how you can test it:

contract GasPriceExample {

    uint public gasprice;
    function sendETH() public payable {
  	  gasprice = tx.gasprice;

Deploy the contract and call the gasprice variable. You’ll notice it is set to 0 at first.

Then set the VALUE in the Remix’s Deployer to any amount of ETH you want (remember you only have 100 per account in Remix).

Call the sendETH() function and check the gasprice again afterward. You’ll see how it gives away 1. This is the amount of gas you consumed to send some ETH to the contract.

You may also notice that every time you send a transaction, the account in the Deployer gets 1 WEI less than before. That’s the gas you’re consuming in the testnet on every function call.


Like msg.sender, the tx.origin returns the address that sends a transaction. In contrast with msg.sender, the tx.origin only returns the address when it is a wallet or account. That is, you can’t return the address of a contract with tx.origin.

contract OriginExample {

    address public origin;
    function setOrigin() public {
  	  origin = tx.origin;

Here, you can deploy and call the origin variable. It’s set to 0. Then call the setOrigin() function and the origin afterward. You’ll notice it’s changed to the account you’re using to interact with the contract.

You may be asking… What’s tx.origin useful for?

You can use it to block people from using a certain function through a smart contract given it only returns the address of accounts. Also, given it returns only accounts, you can record which accounts did it.

Learn Variables and Master Solidity!

Variables in Solidity can be complicated. And they’re surely tons of them to consider and learn from.

Luckily, an article like this that tries to explain EVERYTHING you need to know about them helps a lot.

And now that you’re more familiar with Solidity variables, you’re closer to becoming a master Solidity developer.

Be aware there’s a lot more to learn about Solidity before you can write your own contracts. In the next articles, I’m going to talk about arrays, functions, modifiers, parameters, and a lot more.

Stay tuned 😊


Anatomy of smart contracts |
An in-depth look into the anatomy of a smart contact – the functions, data, and variables.
Types — Solidity 0.8.7 documentation
Style Guide — Solidity 0.8.9 documentation
Cheatsheet — Solidity 0.8.9 documentation
Solidity Tutorial : all about Bytes
Bytes are easy to work with in Solidity because they are treated a lot like an array. You can just decode the bytes in the frontend and less data is stored on the blockchain. In computing, the term…
Units and Globally Available Variables — Solidity 0.8.9 documentation
Smart Contract Best Practices Revisited: Block Number vs. Timestamp
Last November, Spankchain, a blockchain for the adult entertainment industry, they informed investors that they had extrapolated the end date two days ahead. The culprit? With the assumptions that…
Solidity - Special Variables
Solidity - Special Variables, Special variables are globally available variables and provides information about the blockchain. Following is the list of special variables −
Working with Strings in Solidity | Hacker Noon
This is the first in a series of blogs we’re going to bring to you directly from the trenches, going into some of the nitty-gritty technical detail of some of the things we’re doing with the Protocol at the moment. Today’s article comes from Alex Pinto, a recent addition to the blockchain engineerin…
What you need to know about `msg` global variables in Solidity
So, you’re new to Solidity and just starting to build smart contracts on the Ethereum blockchain? Awesome! You’ve likely read through the Solidity docs or played around with introductory tutorials…
Data Locations - Storage, Memory and Calldata | Solidity by Example | 0.8.3
Solidity: Variables, Modifiers and Types. » Security Grind
Here we will discuss about the different kinds of variables and types that can be utilized within Solidity to develop smart contracts. First we will...
Contracts — Solidity 0.6.0 documentation
Learn Solidity: Variables (Part 3)
Welcome to another article in the Learn Solidity series. In the last article, we have seen how data location works and when you can use each of the three locations: memory, storage, and calldata. In…