ref('model_name') is a Jinja function that points at another dbt model. dbt replaces it with the correct database/schema/table name for the current environment, and uses it to build the dependency DAG.
Example
-- models/marts/fct_orders.sql
select
o.order_id,
c.customer_name,
o.amount
from {{ ref('stg_orders') }} o
join {{ ref('stg_customers') }} c
on o.customer_id = c.customer_idCompiled in dev might become:
from analytics_dev.stg_orders o join analytics_dev.stg_customers c ...
In prod it becomes analytics.stg_orders, etc.
Why not hardcode analytics.stg_orders?
1. Environments: dev / ci / prod schemas differ; ref resolves them. 2. DAG: dbt only knows fct_orders depends on stg_orders if you use ref. 3. Selection: dbt run --select +fct_orders walks the graph from ref edges. 4. Renames / packages: model locations can move without rewriting every SQL file.
Mental model
Interview tip: "Always ref models you build with dbt. Hardcoded names break multi-env workflows and hide dependencies from the DAG."