Both tools filter structured data with a similar language, so the question looks like a choice. It usually isn't. jq reads JSON; yq reads YAML and four other formats, and writes them back with your comments intact. Most people end up with both installed.
This compares them where they actually differ: file formats, syntax compatibility, in-place editing, and speed. Everything below was run with jq 1.8.2 and yq v4.53.6 (the Go one) on the same files.
## The short answer
**Reach for jq**
For JSON from APIs, logs, and cloud CLIs. It's faster on large files, it's already installed almost everywhere, and its filter language is the one every other tool imitates.
--card--
**Reach for yq**
For YAML: Kubernetes manifests, CI pipelines, Docker Compose, Helm values. It edits files in place and keeps comments, which jq cannot do at all.
--card--
**Use both**
`yq -o=json` converts, then jq does the heavy filtering. This is the practical answer when a YAML file needs jq's full function set.
If you install one, install jq: more of the data you meet is JSON, and more documentation assumes it. Add yq the first time you need to change a YAML file without mangling it.
## The contenders
**jq** is a JSON processor with a filter language built for streaming transformation. It reads and writes JSON only.
**yq** is a YAML processor that borrows jq's syntax. It reads and writes YAML, JSON, XML, TOML, properties, and CSV, and it can modify a file in place.
One trap before anything else: **there are two different tools named yq.** The one here is [mikefarah/yq][yq-repo], written in Go, installed by Homebrew as `yq`. The other is [kislyuk/yq][yq-python], a Python wrapper that converts YAML and shells out to jq, so it takes real jq syntax. Check which one you have:
```bash
yq --version
# yq (https://github.com/mikefarah/yq/) version v4.53.6
```
Advice written for one is often wrong for the other. Everything below is the Go one.
## Where they differ
### jq cannot read YAML
This is the whole reason yq exists:
```bash
jq '.image' app.yaml
```
```text
jq: parse error: Invalid numeric literal at line 1, column 2
```
```bash
yq '.image' app.yaml
```
```text
ghcr.io/acme/payments:1.4.2
```
### yq edits in place and keeps your comments
Given a file with comments:
```yaml
# Deployment settings for the payments API.
name: payments-api
replicas: 2 # bumped for Black Friday
```
```bash
yq -i '.replicas = 5' app.yaml
head -3 app.yaml
```
```text
# Deployment settings for the payments API.
name: payments-api
replicas: 5 # bumped for Black Friday
```
The value changed, both comments survived, and no temporary file was involved. jq has no `-i`, and a round trip through jq would discard every comment because JSON has none.
### The syntax overlaps, until it doesn't
Most filters are identical. `select`, `map`, pipes, and object construction all behave the same way, which is why yq feels familiar. Then you hit a jq builtin yq doesn't have:
```bash
yq '[.a[]] | add' data.json
```
```text
Error: 1:10: lexer: invalid input text "add"
```
yq has no `add`. The equivalent needs a reduce:
```bash
yq '.a[] as $i ireduce(0; . + $i)' data.json
```
```text
6
```
Treat the languages as similar dialects, not drop-in replacements. Filters you copy from a jq answer on Stack Overflow may need translation.
### jq is faster on large JSON
Same filter, same 17 MB file of 200,000 records, best of three runs:
```bash
# jq: 0.31s
jq '[.users[] | select(.dept == "eng" and .active) | .salary] | add' big.json
# yq: 1.04s
yq '[.users[] | select(.dept == "eng" and .active) | .salary] | .[] as $i ireduce(0; . + $i)' big.json
```
Both printed `5266732729`. jq finished about 3.4 times sooner. At this size the difference is a second; in a loop over hundreds of files it stops being trivia.
jq also streams, so it can work through a file larger than memory:
```bash
jq --stream -n 'first(inputs | select(length==2 and .[0][2]=="name") | .[1])' big.json
```
yq has no streaming equivalent: it builds the document in memory.
### yq reads formats jq has never heard of
```bash
printf '8080' | yq -p=xml -o=json
```
```json
{
"config": {
"port": "8080"
}
}
```
It does the same for TOML, properties files, and CSV, and writes any of them back out with `-o=`. For jq, anything that isn't JSON needs a converter first.
### Multi-document YAML
Kubernetes manifests separated by `---` are one file with several documents. yq handles that natively:
```bash
yq '.name' multi.yaml
```
```text
a
---
b
---
c
```
Converting to JSON flattens them into a stream that jq reads one at a time:
```bash
yq -o=json multi.yaml | jq -r '.name'
```
```text
a
b
c
```
### Error messages
```bash
echo '{}' | jq '.a |'
```
```text
jq: error: syntax error, unexpected end of file at , line 1, column 4:
.a |
^
```
```bash
echo '{}' | yq '.a |'
```
```text
Error: '|' expects 2 args but there is 1
```
jq points at the character. yq describes the problem. Neither is bad, though jq's caret is quicker to act on in a long filter.
## Trade-offs
**jq**
{{< procon >}}
- Installed almost everywhere, including most CI images
- Fastest on large JSON, and the only one that streams
- The reference filter language: most examples and answers use it
- Precise syntax errors with a caret under the fault
--cons--
- JSON only: no YAML, XML, or TOML
- No in-place editing; write to a temp file and move it
- Comments cannot survive, because JSON has none
{{< /procon >}}
**yq**
{{< procon >}}
- Reads and writes YAML, JSON, XML, TOML, properties, and CSV
- Edits files in place and preserves comments and key order
- Handles multi-document YAML, the Kubernetes default
- Familiar syntax if you already know jq
--cons--
- Slower on large JSON, roughly 3x in the test above
- Missing jq builtins such as `add`, so filters need translating
- Two unrelated tools share the name, and advice for one misleads for the other
- Another dependency to install on servers and CI images
{{< /procon >}}
## Pick by the job
**API responses, logs, cloud CLI output**
jq. It's JSON, jq is installed, and it's the fastest option.
--card--
**Change a value in a Kubernetes manifest or CI file**
yq, with `-i`. It's the only one that edits in place and keeps comments.
--card--
**Complex query over a YAML file**
`yq -o=json` piped into jq, so you get yq's reader and jq's full function set.
--card--
**Convert between formats**
yq. XML or TOML to JSON is one flag; jq would need a separate converter.
## Related Content
* [jq Cookbook][jq-cookbook] has the filters both tools mostly share.
* [How to Use jq With kubectl][jq-kubectl] covers querying a cluster, where `kubectl -o json | jq` and `kubectl -o yaml | yq` both apply.
* [What Is Nushell?][nushell] is the other answer to this problem: a shell where structured data never becomes text in the first place.
## References
* [mikefarah/yq][yq-repo] and its [documentation][yq-docs], the Go implementation used here.
* [kislyuk/yq][yq-python], the Python wrapper that shares the name and takes jq syntax.
* [jq manual][jq-manual], for the builtins yq may not have.
[jq-cookbook]: https://jeffbailey.us/learn-jq/
[jq-kubectl]: https://jeffbailey.us/how-do-i-use-jq-with-kubectl/
[nushell]: https://jeffbailey.us/what-is-nushell/
[yq-repo]: https://github.com/mikefarah/yq
[yq-docs]: https://mikefarah.gitbook.io/yq/
[yq-python]: https://github.com/kislyuk/yq
[jq-manual]: https://jqlang.org/manual/