Skip to the page
Tecyfy

dbt interview question · easy

Why does dbt use ref() and source() instead of table names? A new engineer has written FROM analytics.stg_orders in a model and asks why the reviewer wants {{ ref('stg_orders') }}.

The short answerref() does 2 things a table name cannot.

The full answer

ref() does 2 things a table name cannot. It builds the dependency graph: dbt reads every ref to learn that fct_orders needs stg_orders, so dbt run and dbt build run models in the right order, and selectors such as stg_orders+ know what is downstream. And it resolves the schema at run time, so the same code writes to a developer's schema, the CI schema and production without edits. A hard-coded analytics.stg_orders breaks both: dbt may build fct_orders before stg_orders, and a developer's run reads production data. source() does the same for raw tables loaded by other tools, and adds lineage, tests and freshness checks on them.

1. The difference in one model

-- breaks: no dependency, always reads production
select * from analytics.stg_orders

-- right: dbt knows the dependency and picks the schema of this run
select * from {{ ref('stg_orders') }}

In a developer's run ref('stg_orders') might compile to dev_ana.stg_orders; in production to analytics.stg_orders.

2. What the graph gives you

  • Order: models build after everything they ref.
  • Selection: dbt build --select stg_orders+ builds a model and everything that depends on it; +fct_orders builds what it depends on.
  • Skipping on failure: in dbt build, a failing test on stg_orders skips the models downstream of it instead of building them on bad data.
  • Lineage: the docs site draws the graph from the same references.

3. Sources for raw data

# models/staging/sources.yml
sources:
  - name: shop
    schema: raw_shop
    tables:
      - name: orders
        loaded_at_field: _loaded_at
        freshness:
          warn_after: {count: 12, period: hour}
select * from {{ source('shop', 'orders') }}

The raw table now appears in the lineage, can carry data tests such as unique on its id, and dbt source freshness warns when the loader has not delivered for 12 hours.

4. The rule

Inside a dbt project, a model never names a table directly: models are read with ref, raw tables with source. A reviewer can check it with a quick search for from followed by a schema name.

What interviewers want: the dependency graph and run order, the schema chosen per environment, selection with + and skipping after failed tests, source() for raw tables with lineage, tests and freshness.

Last updated 16 October 2026