What is the difference between JSON and JSONB in PostgreSQL?
JSON stores the exact text you inserted, including spaces, key order and duplicate keys, and re-parses it on every read. JSONB stores a parsed binary form, which is slightly slower to write but much faster to query and can be indexed with GIN. Use JSONB unless you must keep the original text exactly.
Explain it simply
JSON is like saving a photocopy of a handwritten form. You keep it exactly as written, but every time someone asks "what is the phone number?", you have to read the whole page again. JSONB is like typing the form into a spreadsheet once. You lose the handwriting and the order the fields were written in, but finding any field afterwards is quick, and you can build an index on it.
Example
CREATE TABLE products (
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
name text NOT NULL,
attributes jsonb NOT NULL DEFAULT '{}'
);
INSERT INTO products (name, attributes) VALUES
('Kurta', '{"size": "M", "colour": "blue", "tags": ["cotton", "festive"]}'),
('Phone', '{"brand": "Lava", "ram_gb": 6}');
-- -> returns jsonb, ->> returns text
SELECT name, attributes->>'colour' AS colour FROM products;
-- Containment: does the document contain this key/value?
SELECT name FROM products WHERE attributes @> '{"brand": "Lava"}';
-- Key existence
SELECT name FROM products WHERE attributes ? 'size';
-- Index to make @> and ? fast
CREATE INDEX idx_products_attributes ON products USING gin (attributes);The difference in storage shows up here:
SELECT '{"b": 1, "a": 2, "a": 3}'::json; -- {"b": 1, "a": 2, "a": 3}
SELECT '{"b": 1, "a": 2, "a": 3}'::jsonb; -- {"a": 3, "b": 1}How it works
| JSON | JSONB | |
|---|---|---|
| Stored as | Exact input text | Parsed binary format |
| Whitespace and key order | Kept | Not kept |
| Duplicate keys | All kept | Last value wins |
| Insert speed | Slightly faster | Slightly slower (parsing) |
| Query speed | Slower, parsed every time | Faster |
GIN indexing and operators like @>, ? | No | Yes |
Both types validate that the input is valid JSON. Most JSON functions have a jsonb_ version, such as jsonb_set to update one key and jsonb_array_elements to turn an array into rows.
Real-world use
- Product attributes that differ by category: a phone has RAM, a kurta has a size.
- Storing raw webhook payloads from a payment gateway, alongside a few extracted columns you query often.
- User settings or feature flags that change shape over time.
Use plain JSON only when you need the exact original text back, for example to verify a signature that was computed on the raw payload.
Common mistakes
- Putting everything in one JSONB column. Fields you filter, join or sort on regularly, such as
user_idorcreated_at, belong in normal typed columns with constraints and foreign keys. - Comparing
attributes->'ram_gb' = 6. The->operator returns jsonb. Use(attributes->>'ram_gb')::int = 6, or containmentattributes @> '{"ram_gb": 6}'. - Expecting a GIN index to speed up
attributes->>'colour' = 'blue'. The default GIN index supports@>,?,?|and?&. For one specific key, create a B-tree expression index on(attributes->>'colour').
What to say in the interview
"JSON stores the input text as is, keeping whitespace, key order and duplicates, and it is parsed every time you query it. JSONB stores a decomposed binary form, drops whitespace and duplicate keys, and is a bit slower to insert but much faster to query. JSONB supports GIN indexes and operators like containment and key existence. I default to JSONB, keep frequently queried fields as real columns, and use JSON only if I need the original text exactly."
Follow-up questions
- What is the difference between
->and->>? - How do you update a single key inside a JSONB column?
- When would you choose a separate table instead of a JSONB column?
Go deeper
Read these once the basics above feel easy.
GIN has a second operator class, jsonb_path_ops: CREATE INDEX ON products USING gin (attributes jsonb_path_ops);. It is smaller and faster for @> queries but does not support the key-existence operators. PostgreSQL also supports SQL/JSON path queries such as jsonb_path_query(attributes, '$.tags[*]') and the @? and @@ operators, which the default GIN operator class can also accelerate.