---
title: Maps
description: "The composite type map is used as a way to associate keys with corresponding values of various types."
---

import { Badge } from '@astrojs/starlight/components';

The [composite type](/book/types#composite-types) `map<K, V>{:tact}` is used as a way to associate keys of type `K{:tact}` with corresponding values of type `V{:tact}`.

For example, `map<Int, Int>{:tact}` uses the [`Int{:tact}`][int] type for its keys and values:

```tact
struct IntToInt {
    counters: map<Int, Int>;
}
```

Since maps can use any given [struct][struct] or [message struct][message] as their [value types](#allowed-types), nested maps can be created via helper structures like this:

```tact
// A `map<Address, Int>` packed into the `AllowanceMap` structure
struct AllowanceMap { unbox: map<Address, Int> }

contract NestedMaps {
    // Empty receiver for the deployment,
    // which forwards the remaining value back to the sender
    receive() { cashback(sender()) }

    get fun test(): Int {
        // An outer map `map<Address, AllowanceMap>`,
        // with `AllowanceMap` structs as values,
        // each containing maps of type `map<Address, Int>`
        let allowances: map<Address, AllowanceMap> = emptyMap();

        // An inner map in the `unbox` field of the `AllowanceMap` struct
        let allowance = AllowanceMap { unbox: emptyMap() };

        // Setting the inner map entry
        allowance.unbox.set(myAddress(), 42);

        // Setting the outer map entry
        allowances.set(myAddress(), allowance);

        // Produces 42
        return allowances.get(myAddress())!!.unbox.get(myAddress())!!;
    }
}
```

Keep in mind that on [TVM][tvm], maps are represented as the [`Cell{:tact}`][cell] type, which is very gas-intensive. Also, nested maps will reach the [limits](#limits-and-drawbacks) faster than regular maps.

## Allowed types

Allowed key types:

* [`Int{:tact}`][int]
* [`Address{:tact}`][p]

Allowed value types:

* [`Int{:tact}`][int]
* [`Bool{:tact}`](/book/types#booleans)
* [`Cell{:tact}`][cell]
* [`Address{:tact}`][p]
* any [struct][struct] type
* any [Message][message] type

Neither key nor value types can be made [optional](/book/optionals).

```tact
// COMPILATION ERROR! Map key types cannot be optional
let myMap: map<Int?, Int> = emptyMap();
//             ~~~~
```

## Serialization

It is possible to perform [integer serialization](/book/integers#common-serialization-types) of map keys, values, or both to [preserve space and reduce storage costs](/book/integers#serialization):

```tact
struct SerializedMapInside {
    // Both keys and values here are serialized as 8-bit unsigned integers,
    // thus preserving space and reducing storage costs:
    countersButCompact: map<Int as uint8, Int as uint8>;
}
```

Since map keys can only be of fixed width, [variable integer types](/book/integers#serialization-varint) are not allowed for them. Instead, use [fixed-width serialization formats](/book/integers#serialization-fixed).

However, map values of type [`Int{:tact}`][int] can have either [fixed](/book/integers#serialization-fixed) or [variable](/book/integers#serialization-varint) bit-length serialization formats specified.

No other [allowed key or value types](#allowed-types) besides [`Int{:tact}`][int] have serialization formats available.

:::note

  Read more about serialization in Tact: [Compatibility with FunC](/book/func#convert-serialization).

:::

## Low-level representation

On [TVM][tvm], maps are represented as a [`Cell{:tact}`][cell] type, and it's possible to construct and parse them directly. However, doing so is highly error-prone and quite messy, which is why Tact provides maps as a standalone composite type with many of the helper methods [described below](#operations).

Essentially, maps are cells with a special bits layouts, which have many supporting TVM instructions for their modification and usage. They are broadly called ["hashmaps"](https://docs.ton.org/v3/documentation/data-formats/tlb/tl-b-types#hashmap), although they are closer to balanced trees.

Maps always have fixed key lengths which can be one of two kinds: long and short. For the distinction and composition of maps with either of key types, see the [description of the `.deepEquals(){:tact}`](#deepequals) map method.

## Operations

### Declare, `emptyMap()` {#emptymap}

```tact
// K and V correspond to the key and value types of the target map
fun emptyMap(): map<K, V>;
```

Declaring a map as a [local variable](/book/statements#let), using the `emptyMap(){:tact}` function from the standard library:

```tact
let fizz: map<Int, Int> = emptyMap();
let fizz: map<Int, Int> = null; // identical to the previous line, but less descriptive
```

Declaring a map as a [persistent state variable](/book/contracts#variables):

```tact
contract Example {
    fizz: map<Int, Int>; // Int keys to Int values
    init() {
        self.fizz = emptyMap(); // redundant and can be removed!
    }
}
```

Note that [persistent state variables](/book/contracts#variables) of type `map<K, V>{:tact}` are initialized empty by default and do not need default values or initialization in the [`init(){:tact}` function](/book/contracts#init-function).

### Initialize with a map literal {#initialize}

<Badge text="Available since Tact 1.6.7" variant="tip" size="medium"/><p/>

```tact
// K and V correspond to the key and value types of the target map
map<K, V> { key1: value1, key2: value2, /* ... */, keyN: valueN };
```

Map literals are expressions that provide a concise and gas-effective way to create maps. First goes the `map<K, V>{:tact}` type expression, then a curly-brace enclosed comma-delimited list of zero or more predefined key-value pairs.

```tact
// Declaring a map as a local variable via a map literal expression
let myMap: map<Int as uint8, Int as uint16> = map<Int as uint8, Int as uint16> {
    // Key: Value
    1: 100, // key 1, value 100
    2: 200, // key 2, value 200
};

// However, if you specify the type of ascription in the let statement,
// it should exactly match the one in the map literal, or it will not compile.
let mismatch: map<Address, Cell> = map<Address, Int> {}; // COMPILATION ERROR! Type mismatch
```

Map literals with an empty body are also supported. They produce the same value as the [`emptyMap(){:tact}`](#emptymap) function and also require type ascriptions for the variable definition.

```tact
let myMap: map<Int as uint8, Cell> = map<Int as uint8, Cell> {};
let myMap2: map<Int as uint8, Cell> = emptyMap();

myMap == myMap2; // true
```

Since map literals define map entries at compile-time, they are significantly less gas consuming compared to a series of consecutive [`.set(){:tact}`](#set) calls.

```tact
// If at least some entries are known in advance, prefer doing this
let myMap: map<Int as uint8, Int as uint16> = map<Int as uint8, Int as uint16> {
    1: 100,
    2: 200,
};

// Over this
let myMap2: map<Int as uint8, Int as uint16> = emptyMap();
myMap2.set(1, 100);
myMap2.set(2, 200);

// Because maps composed either way are equivalent
myMap == myMap2; // true
```

Notice that keys and values of the [`Int{:tact}`][int] type must be defined with [storage annotations via `as{:tact}` keyword](/book/integers#serialization).

```tact
let smartMoney: map<Int as uint8, Int as coins> = map<Int as uint8, Int as coins> {};

// Notice that annotations are checked and
let tryAndSerializeMe = map<Int as uint1, Int as uint8> {
    1: 100,
    2: 200, // COMPILATION ERROR! bitLength is too small
};

// If you explicitly specify the type of ascription in the let statement,
// it should exactly match the one in the map literal, or it will not compile.
let nope: map<Int as uint8, Int as coins> = map<Int as int257, Int as varuint32> {};
//                                          ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//                                          COMPILATION ERROR! Type mismatch
```

When using the same key for the multiple entries, the last occurrence of each key-value pair takes precedence. That is, if there are multiple entries with the same key, only the last one will be used.

```tact
let allTheSame: map<Int as int16, Int as int16> = map<Int as int16, Int as int16> {
    1: 100,
    1: 200,
    1: 300,
    1: 400,
};
allTheSame.get(1)!!; // 400
```

Both keys and values can be arbitrary compile-time expressions as long as their resulting types match the respective key-value types of the map literal.

```tact
fun getExchangesList(): map<Address, Cell> {
    return map<Address, Cell> {
        address("UQD5vcDeRhwaLgAvralVC7sJXI-fc2aNcMUXqcx-BQ-OWi5c"): "One Known eXchange".asCell(),
        address("UQABGo8KCza3ea8DNHMnSWZmbRzW-05332eTdfvW-XDQEmnJ"): "yRacket".asCell(),
    };
}
```

:::note

  Support for runtime initialization values that are not resolved at [compile-time](/ref/core-comptime) is planned for future Tact releases.

:::

### Set values, `.set()` {#set}

```tact
// K and V correspond to the key and value types of the given map
extends mutates fun set(self: map<K, V>, key: K, val: V?);
```

To set or replace the value under a key, call the `.set(){:tact}` [method](/book/functions#extensions), which is accessible for all maps.

```tact
// Empty map
let fizz: map<Int, Int> = emptyMap();

// Setting a couple of values under different keys
fizz.set(7, 7);
fizz.set(42, 42);

// Overriding one of the existing key-value pairs
fizz.set(7, 68); // key 7 now points to value 68
```

### Get values, `.get()` {#get}

```tact
// K and V correspond to the key and value types of the given map
extends fun get(self: map<K, V>, key: K): V?;
```

You can check if a key is found in the map by calling the `.get(){:tact}` [method](/book/functions#extensions), which is accessible for all maps. This will return [`null{:tact}`](/book/optionals) if the key is missing or the value if the key is found.

```tact
// Empty map
let fizz: map<Int, Int> = emptyMap();

// Setting a value
fizz.set(68, 0);

// Getting the value by its key
let gotButUnsure: Int? = fizz.get(68);          // returns Int or null, therefore the type is Int?
let mustHaveGotOrErrored: Int = fizz.get(68)!!; // explicitly asserting that the value must not be null,
                                                // which may crash at runtime if the value is, in fact, null

// Alternatively, we can check for the key in an if statement
if (gotButUnsure != null) {
    // Hooray, let's use !! without fear now and cast Int? to Int
    let definitelyGotIt: Int = fizz.get(68)!!;
} else {
    // Do something else...
}
```

### Replace values, `.replace()` {#replace}

<Badge text="Available since Tact 1.6" variant="tip" size="medium"/><p/>

```tact
// K and V correspond to the key and value types of the given map
extends mutates fun replace(self: map<K, V>, key: K, val: V): Bool;
```

To replace the value associated with a key, if such a key exists, use the `.replace(){:tact}` [method](/book/functions#extensions). It returns `true{:tact}` upon successful replacement and `false{:tact}` otherwise.

```tact
// Empty map
let fizz: map<Int, Int> = emptyMap();

// Setting a couple of values under different keys
fizz.set(7, 70);
fizz.set(42, 42);

// Overriding one of the existing key-value pairs
let replaced1 = fizz.replace(7, 68); // key 7 now points to value 68
replaced1; // true

// Trying to replace the value of a non-existing key does nothing
let replaced2 = fizz.replace(8, 68); // no key 8, so nothing was altered
replaced2; // false
```

If the given value is [`null{:tact}`](/book/optionals) and the key exists, the entry is deleted from the map.

```tact
// Empty map
let fizz: map<Int, Int> = emptyMap();

// Setting a couple of values under different keys
fizz.set(7, 70);
fizz.set(42, 42);

// Overriding one of the existing key-value pairs
let replaced1 = fizz.replace(7, null); // the entry under key 7 is now deleted
replaced1; // true

// Trying to replace the value of a non-existing key does nothing
let replaced2 = fizz.replace(8, null); // no key 8, so nothing was altered
replaced2; // false
```

### Replace and get old value, `.replaceGet()` {#replaceget}

<Badge text="Available since Tact 1.6" variant="tip" size="medium"/><p/>

```tact
// K and V correspond to the key and value types of the given map
extends mutates fun replaceGet(self: map<K, V>, key: K, val: V): V?;
```

Like [`.replace()`](#replace), but instead of returning a [`Bool{:tact}`](/book/types#booleans), it returns the old (pre-replacement) value on a successful replacement and [`null{:tact}`](/book/optionals) otherwise.

```tact
// Empty map
let fizz: map<Int, Int> = emptyMap();

// Setting a couple of values under different keys
fizz.set(7, 70);
fizz.set(42, 42);

// Overriding one of the existing key-value pairs
let oldVal1 = fizz.replaceGet(7, 68); // key 7 now points to value 68
oldVal1; // 70

// Trying to replace the value of a non-existing key-value pair will do nothing
let oldVal2 = fizz.replaceGet(8, 68); // no key 8, so nothing was altered
oldVal2; // null
```

If the given value is [`null{:tact}`](/book/optionals) and the key exists, the entry will be deleted from the map.

```tact
// Empty map
let fizz: map<Int, Int> = emptyMap();

// Setting a couple of values under different keys
fizz.set(7, 70);
fizz.set(42, 42);

// Overriding one of the existing key-value pairs
let oldVal1 = fizz.replaceGet(7, null); // the entry under key 7 is now deleted
oldVal1; // 70

// Trying to replace the value of a non-existing key-value pair will do nothing
let oldVal2 = fizz.replaceGet(8, null); // no key 8, so nothing was altered
oldVal2; // null
```

### Delete entries, `.del()` {#del}

```tact
// K and V correspond to the key and value types of the given map
extends mutates fun del(self: map<K, V>, key: K): Bool;
```

To delete a single key-value pair (a single entry), use the `.del(){:tact}` [method](/book/functions#extensions). It returns `true{:tact}` in the case of successful deletion and `false{:tact}` otherwise.

```tact
// Empty map
let fizz: map<Int, Int> = emptyMap();

// Setting a couple of values under different keys
fizz.set(7, 123);
fizz.set(42, 321);

// Deleting one of the keys
let deletionSuccess: Bool = fizz.del(7); // true, because the map contained the entry under key 7
fizz.del(7);                             // false, because the map no longer has an entry under key 7

// Note that assigning the `null` value to a key when using the `.set()` method
//   is equivalent to calling `.del()`, although this approach is much less descriptive
//   and is generally discouraged:
fizz.set(42, null); // the entry under key 42 is now deleted
```

To delete all the entries from the map, re-assign the map using the `emptyMap(){:tact}` function:

```tact
// Empty map
let fizz: map<Int, Int> = emptyMap();

// Setting a couple of values under different keys
fizz.set(7, 123);
fizz.set(42, 321);

// Deleting all of the entries at once
fizz = emptyMap();
fizz = null; // identical to the previous line, but less descriptive
```

With this approach, all previous entries of the map are completely discarded from the contract, even if the map was declared as a persistent state variable. As a result, assigning maps to `emptyMap(){:tact}` **does not** incur any hidden or sudden [storage fees](https://docs.ton.org/develop/smart-contracts/fees#storage-fee).

### Check if entry exists, `.exists()` {#exists}

<Badge text="Available since Tact 1.5" variant="tip" size="medium"/><p/>

```tact
// K and V correspond to the key and value types of the given map
extends fun exists(self: map<K, V>, key: K): Bool;
```

The `.exists(){:tact}` [method](/book/functions#extensions) on maps returns `true{:tact}` if a value under the given key exists in the map and `false{:tact}` otherwise.

```tact
let fizz: map<Int, Int> = emptyMap();
fizz.set(0, 0);

if (fizz.exists(2 + 2)) { // false
    dump("Something doesn't add up!");
}

if (fizz.exists(1 / 2)) { // true
    dump("I told a fraction joke once. It was half funny.");
}

if (fizz.get(1 / 2) != null) { // also true, but consumes more gas
    dump("Gotta pump more!");
}
```

:::note

  Calling `m.exists(key){:tact}` is more gas-efficient than executing `m.get(key) != null{:tact}`, although both approaches yield the same results.

:::

### Check if empty, `.isEmpty()` {#isempty}

```tact
// K and V correspond to the key and value types of the given map
extends fun isEmpty(self: map<K, V>): Bool;
```

The `.isEmpty(){:tact}` [method](/book/functions#extensions) on maps returns `true{:tact}` if the map is empty and `false{:tact}` otherwise:

```tact
let fizz: map<Int, Int> = emptyMap();

if (fizz.isEmpty()) {
    dump("Empty maps are empty, duh!");
}

// Note that comparing the map to `null` behaves the same as the `.isEmpty()` method,
// although such a direct comparison is much less descriptive and is generally discouraged:
if (fizz == null) {
    dump("Empty maps are null, which isn't obvious");
}
```

### Compare with `.deepEquals()` {#deepequals}

<Badge text="500+ gas" title="Uses 500 gas units or more" variant="danger" size="medium"/>
<Badge text="Available since Tact 1.5" variant="tip" size="medium"/><p/>

```tact
// K and V correspond to the key and value types of the given map
extends fun deepEquals(self: map<K, V>, other: map<K, V>): Bool;
```

The `.deepEquals(){:tact}` [method](/book/functions#extensions) on maps returns `true{:tact}` if all entries of the map match corresponding entries of another map, ignoring possible differences in the [underlying serialization logic][hashmap]. Returns `false{:tact}` otherwise.

```tact
let fizz: map<Int, Int> = emptyMap();
let buzz: map<Int, Int> = emptyMap();

fizz.set(1, 2);
buzz.set(1, 2);

fizz.deepEquals(buzz); // true
fizz == buzz;          // true, and uses much less gas to compute
```

Using `.deepEquals(){:tact}` is very important in cases where a map comes from a third-party source that doesn't provide any guarantees about the [serialization layout][hashmap]. For one such example, consider the following code:

```typescript title="some-typescript-code.ts"
// First map, with long labels
const m1 = beginCell()
    .storeUint(2, 2) // long label
    .storeUint(8, 4) // key length
    .storeUint(1, 8) // key
    .storeBit(true)  // value
    .endCell();

// Second map, with short labels
const m2 = beginCell()
    .storeUint(0, 1)           // short label
    .storeUint(0b111111110, 9) // key length
    .storeUint(1, 8)           // key
    .storeBit(true)            // value
    .endCell();
```

Here, both maps are formed manually and both contain the same key-value pair. If you were to send both of these maps in a message to a Tact contract and then compare them with `.deepEquals(){:tact}` and the [equality operator `=={:tact}`](/book/operators#binary-equality), the former would produce `true{:tact}` because both maps have the same entry, while the latter would produce `false{:tact}` as it performs only a shallow comparison of map hashes, which differ since the maps are serialized differently.

:::note

  This function is very gas expensive, and for the majority of cases it will be sufficient to use the shallow comparison via the [equality `=={:tact}`](/book/operators#binary-equality) or [inequality `!={:tact}`](/book/operators#binary-equality) operators.

:::

### Convert to a `Cell`, `.asCell()` {#ascell}

```tact
// K and V correspond to the key and value types of the given map
extends fun asCell(self: map<K, V>): Cell?;
```

To cast maps back to the [underlying](#low-level-representation) [`Cell{:tact}`][cell] type, use the `.asCell(){:tact}` [method](/book/functions#extensions). Since maps are initialized to `null{:tact}`, calling `.asCell(){:tact}` on a map with no values assigned will return `null{:tact}` and **not** an empty [`Cell{:tact}`][cell].

As an example, this method is useful for sending small maps directly in the body of a reply:

```tact
contract Example {
    // Persistent state variables
    fizz: map<Int, Int>; // our map

    // Constructor (initialization) function of the contract
    init() {
        // Setting a bunch of values
        self.fizz.set(0, 3);
        self.fizz.set(1, 14);
        self.fizz.set(2, 15);
        self.fizz.set(3, 926);
        self.fizz.set(4, 5_358_979_323_846);
    }

    // Internal message receiver, which responds to empty messages
    receive() {
        // Here we're converting the map to a Cell and making a reply with it
        self.reply(self.fizz.asCell()!!); // explicitly asserting that the map isn't null
    }
}
```

### Traverse over entries {#traverse}

To iterate over map entries, there is a [`foreach{:tact}`](/book/statements#foreach-loop) loop statement:

```tact {9-11}
// Empty map
let fizz: map<Int, Int> = emptyMap();

// Setting a couple of values under different keys
fizz.set(42, 321);
fizz.set(7, 123);

// Iterating in sequential order: from the smallest keys to the biggest ones
foreach (key, value in fizz) {
    dump(key); // Will dump 7 on the first iteration, then 42 on the second
}
```

Read more about it: [`foreach{:tact}` loop in Book→Statements](/book/statements#foreach-loop).

Note that it is also possible to use maps as simple arrays if you define a `map<Int, V>{:tact}` with an [`Int{:tact}`][int] type for the keys, any allowed `V{:tact}` type for the values, and keep track of the number of items in a separate variable:

```tact
contract Iteration {
    // Persistent state variables
    counter: Int as uint32;    // Counter of map entries, serialized as a 32-bit unsigned integer
    record: map<Int, Address>; // Int to Address map

    // Constructor (initialization) function of the contract
    init() {
        self.counter = 0; // Setting the self.counter to 0
    }

    // Internal message receiver, which responds to a String message "Add"
    receive("Add") {
        // Get the Context struct
        let ctx: Context = context();
        // Set the entry: counter Int as the key, ctx.sender Address as the value
        self.record.set(self.counter, ctx.sender);
        // Increase the counter
        self.counter += 1;
    }

    // Internal message receiver, which responds to a String message "Send"
    receive("Send") {
        // Loop until reaching the value of self.counter (over all the self.record entries)
        let i: Int = 0; // Declare i as usual for loop iterations
        while (i < self.counter) {
            send(SendParameters {
                bounce: false,              // Do not bounce back this message
                to: self.record.get(i)!!,   // Set the sender address, knowing that key i exists in the map
                value: ton("0.0000001"),    // 100 nanoToncoin (nano-tons)
                mode: SendIgnoreErrors,     // Send ignoring any transaction errors
                body: "SENDING".asComment() // String "SENDING" converted to a Cell as a message body
            });
            i += 1; // Don't forget to increase i
        }
    }

    // Getter function for obtaining the value of self.record
    get fun map(): map<Int, Address> {
        return self.record;
    }

    // Getter function for obtaining the value of self.counter
    get fun counter(): Int {
        return self.counter;
    }
}
```

It's often useful to set an upper-bound restriction on such maps, so that you [don't hit the limits](#limits-and-drawbacks).

:::caution

  Note that manually keeping track of the number of items or checking the length of such maps is very error-prone and generally discouraged. Instead, try to wrap your map into a [struct][struct] and define [extension functions](/book/functions#extensions) on it. See an example in the Cookbook: [How to emulate an array using a map wrapped in a struct](/cookbook/data-structures#array).

:::

:::note

  See other examples of map usage in the Cookbook:\
  [How to emulate a stack using a map wrapped in a struct](/cookbook/data-structures#stack)\
  [How to emulate a circular buffer using a map wrapped in a struct](/cookbook/data-structures#circular-buffer)

:::

## Limits and drawbacks

While maps can be convenient to work with on a small scale, they cause a number of issues if the number of items is unbounded and the map significantly grows in size:

* As the upper bound of the smart contract state size is around $65\,000$ items of type [`Cell{:tact}`][cell], it constrains the storage limit of maps to about $30\,000$ key-value pairs for the whole contract.

* The more entries you have in a map, the higher [compute fees](https://docs.ton.org/develop/howto/fees-low-level#computation-fees) you will incur. Thus, working with large maps makes compute fees difficult to predict and manage.

* Using a large map in a single contract does not allow distributing its workload. Hence, this can significantly degrade overall performance compared to using a smaller map along with multiple interacting smart contracts.

To resolve such issues, you can set an upper-bound restriction on a map as a constant and check against it every time you set a new value in the map:

```tact
contract Example {
    // Declare a compile-time constant upper-bound for our map
    const MaxMapSize: Int = 42;

    // Persistent state variables
    arr: map<Int, Int>; // "array" of Int values as a map
    arrLength: Int = 0; // length of the "array", defaults to 0

    // Internal function for pushing an item to the end of the "array"
    fun arrPush(item: Int) {
        if (self.arrLength >= self.MaxMapSize) {
            // Do something, for example, stop the operation
        } else {
            // Proceed with adding a new item
            self.arr.set(self.arrLength, item);
            self.arrLength += 1;
        }
    }
}
```

If you still need a large or unbounded (infinitely large) map, it is better to architect your smart contracts according to the [asynchronous and actor-based model of TON blockchain](https://docs.ton.org/learn/overviews/ton-blockchain). That is, use contract sharding and essentially make the entire blockchain part of your map(s).

{/*
  TODO: Add reference to sharding page as per: https://github.com/tact-lang/tact-docs/issues/155
*/}

[p]: /book/types#primitive-types
[int]: /book/integers
[cell]: /book/cells#cells
[struct]: /book/structs-and-messages#structs
[message]: /book/structs-and-messages#messages

[hashmap]: https://docs.ton.org/develop/data-formats/tl-b-types#hashmap
[tvm]: https://docs.ton.org/learn/tvm-instructions/tvm-overview
