---
title: "GIN Index"
slug: "gin-index"
category: "glossary"
tags: ["glossary", "database", "postgres", "indexing", "gin", "full-text-search", "sql"]
status: "stable"
last_updated: 2026-08-14
summary: "GIN (Generalized Inverted Index): a PostgreSQL index for composite values like tsvectors, JSONB, and arrays, where one row contributes many index entries."
related:
  [
    "[[glossary/btree-index]]",
    "[[glossary/query-plan]]",
    "[[glossary/jsonb-path]]",
    "[[backend/postgres]]",
    "[[cheatsheets/postgres-explain]]",
  ]
---

## Overview

This page is the atomic definition. Indexing strategy and `EXPLAIN` analysis live at [[backend/postgres]] and [[cheatsheets/postgres-explain]]. A GIN index inverts composite values (tsvector lexemes, JSONB keys, array elements) so each element is a separate index entry, enabling fast full-text, containment, and array-overlap queries that B-tree cannot serve.

## Definition

A GIN (Generalized Inverted Index) indexes the elements of composite values. For a `tsvector` column, GIN creates an entry for each lexeme pointing to the rows that contain it; for a `jsonb` column, it indexes each key-value pair; for an `integer[]` column, it indexes each array element. A single row can contribute hundreds of index entries.

GIN supports operators that B-tree does not:
- Full-text: `@@` (tsvector match)
- JSONB containment: `@>`, `<@`, `?`, `?|`, `?&`
- Array operators: `@>`, `<@`, `&&`

GIN trade-offs vs. [[glossary/btree-index|B-tree]]:
- Build is slower; GIN must index many keys per row.
- Updates are slower; GIN uses a pending-list that VACUUM or `gin_clean_pending_list()` merges.
- Queries that hit GIN are faster than sequential full-text search by orders of magnitude on large tables.
- `fastupdate = on` (default) batches inserts into a pending list; flush with vacuum for consistent query performance.

GiST is an alternative to GIN for full-text and geometric queries; GiST builds faster and updates faster but queries slower than GIN for large lexeme sets.

## When it applies

Use GIN on `tsvector` columns for full-text search, on `jsonb` columns queried with containment operators, and on array columns queried with overlap or containment. Use `CREATE INDEX CONCURRENTLY` to avoid locking the table during build.

## Example

```sql
ALTER TABLE articles ADD COLUMN search_vec tsvector
  GENERATED ALWAYS AS (to_tsvector('english', coalesce(title,'') || ' ' || coalesce(body,''))) STORED;

CREATE INDEX idx_articles_fts ON articles USING gin(search_vec);

SELECT id, title FROM articles
WHERE search_vec @@ to_tsquery('english', 'postgres & index');

CREATE INDEX idx_events_data ON events USING gin(data);
SELECT * FROM events WHERE data @> '{"type": "purchase"}';
```

## Related concepts

- [[glossary/btree-index]] - the sibling index type for equality and range; not useful for composite values.
- [[glossary/query-plan]] - `EXPLAIN` shows `Bitmap Index Scan` or `Index Scan` on the GIN.
- [[glossary/jsonb-path]] - JSONB path queries may use GIN if the access pattern matches.
- [[backend/postgres]] - the Postgres deep-dive.
- [[cheatsheets/postgres-explain]] - reading `EXPLAIN` output to confirm GIN usage.

## Citing this term

> See [[glossary/gin-index|GIN Index]] (llmbestpractices.com/glossary/gin-index).

## Related

- [[glossary/btree-index]]
- [[glossary/query-plan]]
- [[glossary/jsonb-path]]
- [[backend/postgres]]
- [[cheatsheets/postgres-explain]]
