Understanding Uniswap V3 Ticks - The Easy Way

A gentle introduction to Uniswap V3's tick system, explaining how concentrated liquidity enables capital-efficient market making through discrete price points.

What you will learn

Tick Definition

Understand what ticks are and how they enable efficient liquidity provision.

Difference Array Technique

Discover the algorithmic foundation that makes Uniswap V3's tick system efficient.

Liquidity Management

Learn how to manage liquidity in Uniswap V3 through the tick system.

Swap Mechanics

See how swaps interact with ticks and how crossing boundaries affects liquidity.

Introduction

Many people, including myself, felt overwhelmed when first encountering the concept of Uniswap V3. Even if you're familiar with Uniswap V2, there are many new concepts and features in Uniswap V3 to understand. One of them is ticks.

You've probably seen descriptions like this: Uniswap V3 revolutionized automated market making (AMM) by introducing concentrated liquidity. Unlike Uniswap V2, where liquidity is spread across the entire price curve (0 to ∞), V3 allows liquidity providers (LPs) to concentrate their capital within specific price ranges.

This definition wasn't very clear to me at first. I tried to understand it better by reading the Uniswap V3 contract code, but quickly felt overwhelmed. The code related to ticks is quite complex. I wanted to find a well-balanced explanation—not too general, but not too complex either.

In this article, I'll explain what ticks are from a conceptual level. I hope this article can help people like my past self understand the concept of ticks easily without feeling intimidated. For implementation details, I'll cover those in a separate article.

One-Dimensional Difference Array Technique

Before diving into how Uniswap V3 uses ticks, let's understand a fundamental algorithmic technique that makes it all possible: the 1D Difference Array. This classic optimization technique efficiently handles range updates on arrays.

The Problem: Imagine you have an array of values (all starting at 0), and you need to add or subtract values across ranges [L, R) many times. Calculate the value of the array after all updates.

Naive Solution: Iterates through every element in each range and updates it, resulting in O(n) time complexity per update. With many updates, this becomes very inefficient.

Efficient Solution: Instead of updating every element, the difference array technique stores only the changes at boundaries. When adding a value V across range [L, R), you simply:
  • Mark +V at index L (where the range begins)
  • Mark -V at index R (where the range ends)

To get the actual value at any index, you accumulate all changes from the beginning. This reduces each range update from O(n) to O(1).

Difference Array in Action

Operations:

  1. Add 100 to range [2, 5)
  2. Add 75 to range [1, 6)
  3. Subtract 50 from range [3, 5)

Delta Array (boundary changes):

Only store what changes at each boundary:

[0]
0
[1]
+75
[2]
+100
[3]
-50
[4]
0
[5]
-50
[6]
-75
[7]
0
[8]
0
[9]
0

Each operation only modifies 2 positions: the start and end boundaries.

For example, value at index 5 is: -100 + 50 = -50

Final Array (accumulated values):

Accumulate deltas from left to right to get actual values:

[0]
0
[1]
75
[2]
175
[3]
125
[4]
125
[5]
75
[6]
0
[7]
0
[8]
0
[9]
0

Understanding Ticks the Easy Way

At its core, ticks are represented as an array. Each element in this array contains information about a specific price point. The most important piece of information stored at each tick is liquidityNet—the change in liquidity at that price point.

To start, you can think of ticks as an array where each index mapping to a discrete price point, and the value at that index tells you how liquidity changes when the price crosses that point. It's similar to the difference array technique we discussed earlier.

Basic Terminology

An Uniswap pool contains two tokens (like ETH/USDC or ETH/BTC). Throughout this article, we'll use ETH/USDC as our example, where:

  • ETH is token X (the base token)
  • USDC is token Y (the quote token)
  • Price means how many USDC per 1 ETH (e.g., price = 2000 means 1 ETH = 2000 USDC)
  • Price range [2000, 3000) means a range of price of the pool is from 2000 to 3000, excluding 3000

Each Tick Represents a Price Point

In Uniswap V3, each tick index corresponds to a specific price. The relationship between a tick index and its price is defined by a mathematical formula. Therefore, some price range can also be represented by a tick range, with each tick corresponding to a specific price.

Our Simplified Model

For this article, we'll use a simple linear formula to make the concepts easier to follow:

price = 4 × tick_index

With this formula, assuming ETH price ranges from $4 to $4,000:

  • Tick 1 = $4
  • Tick 500 = $2,000
  • Tick 750 = $3,000
  • Tick 1,000 = $4,000
Real Uniswap V3 Formula

In the actual Uniswap V3 implementation, the price-to-tick relationship uses an exponential formula:

price = 1.0001tick

This exponential spacing allows the protocol to support an enormous price range (from nearly 0 to nearly infinity) with consistent percentage-based tick spacing. We use a simplified linear formula in this article to make the core concepts easier to understand.

Concentrated Liquidity with a Price Range

Reminder: The key innovation in Uniswap V3 is concentrated liquidity. Instead of spreading capital across all possible prices, liquidity providers can concentrate it within a specific price range where they expect trading to occur.

When providing liquidity in V3, liquidity providers specify:

  • Liquidity amount - How much capital to provide
  • Price range - The minimum and maximum prices for the position
Concentrated liquidity in Uniswap V3

Liquidity providers choose specific price ranges. Notice how different providers' ranges can overlap, creating varying liquidity depths at different price points. The prices in these ranges are not arbitrary—they correspond to specific tick indices.

Operations:

  • Adds 1,000 liquidity in range [$2,000, $3,000) → ticks [500, 750)
  • Adds 2,000 liquidity in range [$2,500, $3,500) → ticks [625, 875)
  • Removes 500 liquidity from range [$2,000, $2,500) → ticks [500, 625)

Resulting Liquidity Array:

The liquidity at each tick index (showing key ticks in the array):

1
0
...
0
499
0
500
500
501
500
...
500
624
500
625
2,500
626
2,500
...
2,500
749
2,500
750
2,000
751
2,000
...
2,000
874
2,000
875
0
876
0
...
0
1000
0

The array shows liquidity at each tick. Ticks with green backgrounds and bold borders (500, 625, 750, 875) are boundary ticks where liquidity changes. Notice how liquidity remains constant between boundaries: 0 before tick 500, then 500 from ticks 500-624, then 2,500 from ticks 625-749, then 2,000 from ticks 750-874, and back to 0 from tick 875 onwards.

Contrast with Uniswap V2: In V2, there is no concept of price ranges. Every liquidity provider's capital is automatically spread across the entire price curve, from 0 to infinity. This means your liquidity is available at all prices, even extremely unlikely ones.

Spread liquidity in Uniswap V2

Consider the same operations in Uniswap V2:

  • Adds 1000 liquidity (spread across all prices)
  • Adds 2000 liquidity (spread across all prices)
  • Removes 500 liquidity (spread across all prices)

Result: Total liquidity = 2,500 (1000 + 2000 - 500) uniformly distributed across the entire price range [$4, $4,000).

Resulting Liquidity Array:

In V2, liquidity is spread uniformly across all ticks (every tick has the same liquidity):

1
2,500
...
2,500
499
2,500
500
2,500
501
2,500
...
2,500
624
2,500
625
2,500
626
2,500
...
2,500
749
2,500
750
2,500
751
2,500
...
2,500
874
2,500
875
2,500
876
2,500
...
2,500
1000
2,500

Unlike V3's concentrated liquidity, V2 spreads the same 2,500 liquidity uniformly across every single tick from 0 to 1000. There are no boundaries or variations—every price point has identical liquidity depth, resulting in capital inefficiency.

Why this matters: By concentrating liquidity where trading actually happens, Uniswap V3 allows liquidity providers to offer deeper liquidity with the same amount of capital as Uniswap V2. This means liquidity providers can earn more fees with less capital. However, if the price moves outside the chosen range, the position stops earning fees until the price returns to that range.

How Swaps Change Current Liquidity

In the previous section, we somehow saw how liquidity is stored at different ticks. But which liquidity value does the pool actually use during a swap? The pool tracks two critical state variables:

  • liquidity - The current active liquidity available for trading
  • tick (or price) - The current price point of the pool

When a swap happens, the price moves up or down depending on the swap direction (buying or selling). As the price crosses tick boundaries (a tick that is tick start or tick end when providing liquidity), the liquidity value must be updated to reflect the new liquidity depth at the new price level.

How Liquidity Changes During a Swap

Initial State:

  • We have the following liquidity distribution
  • Current tick: 600 (price = $2,400)
  • Current liquidity: 500
1
0
...
0
499
0
500
500
501
500
...
500
624
500
625
2,500
626
2,500
...
2,500
749
2,500
750
2,000
751
2,000
...
2,000
874
2,000
875
0
876
0
...
0
1000
0

The array shows liquidity at each tick. Ticks with green backgrounds and bold borders (500, 625, 750, 875) are boundary ticks where liquidity changes. Notice how liquidity remains constant between boundaries: 0 before tick 500, then 500 from ticks 500-624, then 2,500 from ticks 625-749, then 2,000 from ticks 750-874, and back to 0 from tick 875 onwards.

Scenario: Let's trace how liquidity changes during swap

  1. Swap USDC for ETH. Price increases from tick 600 → 624
    • No boundary crossed
    • Liquidity remains: 500
  2. Swap USDC for ETH. Price increases from tick 624 → 700
    • Crossed boundary at tick 625
    • New liquidity: 2500
  3. Swap ETH for USDC. Price decreases from tick 700 → 600
    • Crossed boundary at tick 625
    • New liquidity: 500
  4. Swap ETH for USDC. Price decreases from tick 600 → 500
    • No boundary crossed
    • Liquidity remains: 500
  5. Swap ETH for USDC. Price decreases from tick 500 → 499
    • Crossed boundary at tick 500
    • New liquidity: 0, swap failed

Tick Management in Action

Now that we understand how ticks work conceptually, let's walk through a realistic sequence of operations in an ETH/USDC pool. We'll examine exactly how adding liquidity, removing liquidity, and swapping affect the pool's current tick and current liquidity. For clarity, we'll continue using our simplified model where price = 4 × tick.

Initial Pool State

Let's start with an empty ETH/USDC pool and build it up step by step:

  • Current tick: 500 (price = $2,000)
  • No liquidity positions exist yet
  • Current liquidity: 0 (pool is empty)

Operation 1: Add Liquidity

Add Liquidity Operation

Action:

Adds 1,000 liquidity in range [$2,000, $3,000) → ticks [500, 750)

What happens:

  1. Update tick 500: liquidityNet += 1,000
  2. Update tick 750: liquidityNet -= 1,000
  3. Since current tick (500) is within range, update current liquidity

Pool state after:

  • Current tick: 500 (unchanged - adding liquidity doesn't move price)
  • Current liquidity: 0 + 1,000 = 1,000

liquidityNet Array (shows deltas at boundaries):

1
0
...
0
500
+1,000
...
0
750
-1,000
...
0
1000
0

Only boundaries have non-zero values: tick 500 gets +1,000 (range start) and tick 750 gets -1,000 (range end).

Operation 2: Swap USDC for ETH

Swap Operation (Price Increases)

Action:

Swap USDC for ETH, pushing price from $2,000 to $2,600

What happens:

  1. Price moves from tick 500 → tick 650
  2. No tick boundaries crossed (still within [500, 750))
  3. Current liquidity stays the same

Pool state after:

  • Current tick: 500 → 650 (price moved)
  • Current liquidity: 1,000 (unchanged - no boundary crossed)

Operation 3: Add More Liquidity

Add Liquidity Operation

Action:

Add 2,000 liquidity in range [$2,500, $3,500) → ticks [625, 875)

What happens:

  1. Update tick 625: liquidityNet += 2,000
  2. Update tick 875: liquidityNet -= 2,000
  3. Current tick (650) is within the new range [625, 875), so update current liquidity

Pool state after:

  • Current tick: 650 (unchanged)
  • Current liquidity: 1,000 + 2,000 = 3,000

liquidityNet Array (shows deltas at boundaries):

1
0
...
0
500
+1,000
...
0
625
+2,000
...
0
750
-1,000
...
0
875
-2,000
...
0

First position: +1,000 at tick 500, -1,000 at tick 750. Second position: +2,000 at tick 625, -2,000 at tick 875. All non-boundary ticks remain 0.

Operation 4: Swap ETH for USDC (Crosses Boundary)

Swap Operation (Price Decreases, Crosses Tick)

Action:

Swap ETH for USDC, pushing price from $2,600 down to $2,400

What happens:

  1. Price moves from tick 650 → tick 600
  2. Crosses tick boundary 625 (going left/down)
  3. When crossing tick 625 going left, apply liquidityNet[625] = +2,000
  4. Current liquidity: 3,000 - 2,000 = 1,000

Pool state after:

  • Current tick: 650 → 600 (price decreased)
  • Current liquidity: 3,000 → 1,000 (crossed boundary at 625)

Key insight: When price crosses a boundary moving left (price decreasing), we subtract the liquidityNet value because we're leaving that range.

Operation 5: Add Liquidity Out of Range

Add Liquidity (Out of Range)

Action:

Add 1,500 liquidity in range [$3,600, $4,000) → ticks [900, 1000)

What happens:

  1. Update tick 900: liquidityNet += 1,500
  2. Update tick 1000: liquidityNet -= 1,500
  3. Current tick (600) is outside this range, so current liquidity stays unchanged

Pool state after:

  • Current tick: 600 (unchanged)
  • Current liquidity: 1,000 (unchanged - position is out of range)

Key insight: Adding liquidity outside the current price range doesn't affect current liquidity. This liquidity will only become active when the price moves into that range.

liquidityNet Array (shows deltas at boundaries):

1
0
...
0
500
+1,000
...
0
625
+2,000
...
0
750
-1,000
...
0
875
-2,000
...
0
900
+1,500
...
0
1000
-1,500

Now we have three positions: +1,000 at tick 500/-1,000 at tick 750, +2,000 at tick 625/-2,000 at tick 875, and +1,500 at tick 900/-1,500 at tick 1000. The new position at [900, 1000) doesn't affect current liquidity since current tick is 600.

Operation 6: Remove Partial Liquidity

Remove Liquidity (Partial)

Action:

Remove 400 liquidity from range [$2,000, $3,000) → ticks [500, 750)

What happens:

  1. Update tick 500: liquidityNet -= 400 (from +1,000 to +600)
  2. Update tick 750: liquidityNet += 400 (from -1,000 to -600)
  3. Current tick (600) is within this range, so update current liquidity

Pool state after:

  • Current tick: 600 (unchanged)
  • Current liquidity: 1,000 - 400 = 600

Final liquidityNet Array (shows deltas at boundaries):

1
0
...
0
500
+600
...
0
625
+2,000
...
0
750
-600
...
0
875
-2,000
...
0
900
+1,500
...
0
1000
-1,500

After partial removal, the first position has reduced deltas: +600 at tick 500 and -600 at tick 750. The other positions remain unchanged. At current tick 600, the active liquidity is 600 from the partially removed position.

Operation 7: Swap Crossing Boundary (Price Increase)

Swap Operation (Price Increases, Crosses Tick)

Action:

Swap USDC for ETH, pushing price from $2,400 to $2,800

What happens:

  1. Price moves from tick 600 → tick 700
  2. Crosses tick boundary 625 (going right/up)
  3. When crossing tick 625 going right, apply liquidityNet[625] = +2,000
  4. Current liquidity: 600 + 2,000 = 2,600

Pool state after:

  • Current tick: 600 → 700 (price increased)
  • Current liquidity: 600 → 2,600 (crossed boundary at 625)

Key insight: When price crosses a boundary moving right (price increasing), we add the liquidityNet value because we're entering that range. Now at tick 700, both the first position [500, 750) and second position [625, 875) are active, providing combined liquidity of 600 + 2,000 = 2,600.

Summary of Operations

  • Adding liquidity: Updates liquidityNet at boundaries, increases current liquidity if position includes current tick
  • Removing liquidity: Updates liquidityNet at boundaries, decreases current liquidity if position includes current tick
  • Swapping: Moves current tick (price), updates current liquidity only when crossing tick boundaries

Important Notes:

  • Add/Remove operations are immediate: When adding or removing liquidity, the liquidityNet updates at boundaries take effect immediately, and current liquidity is updated if the current tick falls within the position's range.
  • Swap optimization in practice: In the examples above, we showed swaps crossing boundaries tick by tick for educational clarity. However, in the actual Uniswap V3 implementation, the swap algorithm is optimized—it doesn't iterate through every tick. Instead, it finds the next initialized tick (a tick with non-zero liquidityNet), performs the swap calculation between those initialized ticks, then updates the current liquidity only when crossing an initialized boundary. This makes swaps extremely gas-efficient even with thousands of potential ticks.

Conclusion

In this article, we explored Uniswap V3's tick system from a conceptual perspective, breaking down what seemed like a complex topic into understandable pieces. By using a simplified model (price = 4 × tick) and relating the tick system to the familiar difference array technique, we've seen how Uniswap V3 achieves efficient liquidity management.

The key concepts we covered:

  • The difference array technique as the algorithmic foundation for efficient range updates
  • Ticks as discrete price points where liquidity changes occur
  • How concentrated liquidity allows capital to be focused within specific price ranges
  • How swaps interact with ticks and update current liquidity when crossing boundaries
  • The mechanics of adding, removing liquidity and how it affects the tick system

Understanding these fundamentals provides a solid foundation for working with Uniswap V3. However, this conceptual overview is just the beginning. In a follow-up article, we'll dive into the implementation details—exploring the actual mathematical formulas for tick-to-price conversion, the data structures used in the smart contracts, gas optimization techniques, and how to interact with the protocol programmatically.

For now, I hope this gentle introduction has demystified the tick system and made Uniswap V3 more approachable. The conceptual understanding you've gained here will serve as a strong foundation when you're ready to explore the technical implementation.