Implementing and maintaining external dependencies: sample questions

3 free practice questions on implementing and maintaining external dependencies, one of the seven topics in the official v1.11 outline for the dbt Analytics Engineering Certification Exam — an estimated 6% of the exam, or about 4 of its 65 questions. Answers, reasoning and documentation links are all on this page.

Last updated . We revise these pages whenever dbt Labs revises the exam.

SHARE OF THE EXAM~6%our estimate
QUESTIONS HERE32 with runnable SQL
FORMATS SHOWN3of 6 on the exam

What this topic covers

Two subtopics, both about the edges of the project: implementing exposures, which declare the dashboards and downstream consumers that depend on your models, and implementing source freshness on the data arriving at the other end.

SUBTOPICS, FROM THE OFFICIAL OUTLINE · 2

  1. 01Implementing dbt exposures
  2. 02Implementing source freshness

Where the marks go

Freshness reduces to a single measurement — the age of the newest row, taken as max(loaded_at_field) — compared against two thresholds. The hotspot question asks you to find the table configured so that it can never report a warning, which is what happens when the thresholds are ordered so the error fires first.

Exposures produce documentation and a selector, not warehouse objects. Knowing that they are parsed from a .yml file under an exposures key, and what has to exist before the lineage renders, is the whole of the build-list question.

3 sample questions, with answers

Nothing is hidden. Each question shows the correct answer, why it is right, why every other option is wrong, and the documentation page that settles it. Where the answer is a claim about SQL behaviour, there is a query you can run in the browser against a small sample schema.

Q1Build listMediumRunnable proof

Freshness thresholds and loaded_at_field are already set on every source, and the nightly job builds all models. You want the next job run to build only the models downstream of sources whose maximum loaded_at_field timestamp advanced since the previous freshness check. Arrange the steps. Two steps in the pool do not belong.

In the exam you drag the steps into order. The correct sequence is below.

  1. 1Run dbt source freshness, which writes the current freshness result to target/sources.json
  2. 2Copy that target/sources.json into prod-artifacts/ so the next run has a previous state to compare against
  3. 3On the next job run, run dbt source freshness again to write a new target/sources.json
  4. 4With both artifacts in place, run dbt build --select "source_status:fresher+" --state prod-artifacts
NOT USED
  • Run dbt compile to regenerate target/sources.json before the comparison
  • Run dbt build --select "state:modified+" --state prod-artifacts to pick up the newly loaded rows

WHY

source_status:fresher+ is a state-based selector, so it needs TWO sources.json artifacts: a saved previous one and the current one. The chain is forced by which file exists when. Step 1 produces the first artifact — /reference/commands/source: "When dbt source freshness completes, a JSON file containing information about the freshness of your sources will be saved to target/sources.json." Step 2 must copy that file out of target/ before anything overwrites it, because the next freshness run writes to the same path; with nothing in prod-artifacts/ there is no previous state and the selector has nothing to compare. Step 3 then runs the check again to produce the current max_loaded_at values — /reference/node-selection/methods shows exactly this pairing for dbt v1.11: "dbt source freshness # must be run again to compare current to previous state" followed by "dbt build --select \"source_status:fresher+\" --state path/to/prod/artifacts". Only then does step 4 work: dbt compares max_loaded_at per source ("Max value of loaded_at_field timestamp in the source table when queried", /reference/artifacts/sources-json) between the two files, selects the sources whose value moved forward, and the trailing + pulls in everything downstream of them. Flags follow the subcommand, which is the current supported placement. Precision on what fresher means: sources.json records max_loaded_at — the maximum loaded_at_field value in the source table when queried (https://docs.getdbt.com/reference/artifacts/sources-json) — so source_status:fresher selects sources whose recorded maximum advanced between the two snapshots; late-arriving rows with older loaded_at values do not move it.

WHY THE OTHERS ARE WRONG

Run dbt compile to regenerate target/sources.json before the comparison
dbt compile does not produce a sources.json. The methods page names the only source of that artifact — "The following dbt commands produce sources.json artifacts whose results can be referenced in subsequent dbt invocations: dbt source freshness" — and /reference/artifacts/sources-json lists it as "Produced by: source freshness". compile writes manifest.json, so this step leaves the state directory without the file source_status needs.
Run dbt build --select "state:modified+" --state prod-artifacts to pick up the newly loaded rows
state:modified compares project code, not load times: "The state method is used to select nodes by comparing them against a previous version of the same project, which is represented by a manifest", and "state:modified: All new nodes, plus any changes to existing nodes." A vendor loading fresh rows changes no node in the project, so this selector matches nothing here and the job builds nothing.
Q2HotspotMedium

Consider this exposures.yml block. You want dbt build --select +exposure:weekly_kpis to resolve to the models and sources the dashboard reads. Click the field that puts those upstream nodes in the exposure's lineage.

In the exam you click the region. The answer is highlighted below.

exposures:
- name: weekly_kpis
label: Weekly KPIs
type: dashboard
maturity: high
url: https://bi.example.com/dashboards/42
description: Weekly revenue and retention tiles.
depends_on:
- ref('orders')
- ref('customers')
- source('salesforce', 'accounts')
owner:
name: Data Team
email: data@example.com
Correct region: lines 8–11.

WHY

depends_on is the only field that creates graph edges. Each ref() or source() entry under it registers that node as a parent of the exposure, so the exposure becomes a leaf hanging off orders, customers, and the salesforce.accounts source. That lineage is what the exposure selection method walks: exposure:weekly_kpis selects the exposure node itself, and the + prefix expands to its ancestors — the three declared parents plus everything upstream of them. dbt never inspects the dashboard or its queries, so lineage is exactly what you list here and nothing more. (One caveat on the example itself: the docs note that "While possible, it is highly unlikely you will ever need an exposure to depend on a source directly" — the source() entry here is valid and makes the selection concrete, but a real exposure usually depends on models.)

WHY THE OTHERS ARE WRONG

Line 4
type classifies what the downstream tool is (dashboard, notebook, analysis, ml, application). It drives grouping and display on the docs site; it adds no parents and changes no selector result.
Line 5
maturity is an enumerated trust signal — "One of high, medium, or low" — shown in the docs. dbt validates it, so an invented value is a parsing error rather than an accepted label. Either way it annotates how reliable the exposure is, not what it reads from, and it adds no parents and changes no selector result.
Line 6
url is just a link to the dashboard, rendered on the exposure's docs page so a reader can open the tool. dbt does not fetch it or derive dependencies from it.
Lines 12–14
owner records the name and email of the person or team accountable for the exposure, surfaced in the docs. It is contact metadata, not a graph edge.
Q3MatchingMediumRunnable proof

Consider this sources.yml. You run dbt source freshness. Match each table to the freshness state dbt reports. Two descriptions on the right will remain unused.

sources:
- name: raw_shop
config:
loaded_at_field: _loaded_at
freshness:
warn_after: {count: 6, period: hour}
error_after: {count: 12, period: hour}
tables:
- name: orders
- name: payments
- name: shipments
- name: raw_ref
tables:
- name: regions

In the exam you pair each item yourself. The correct pairings are below.

ITEMMATCHES
raw_shop.orders, newest row 2 hours oldReports pass
raw_shop.payments, newest row 8 hours oldReports warn
raw_shop.shipments, newest row 20 hours oldReports error
raw_ref.regions, newest row 30 days oldReports no state; the table is left out of the check

WHY

dbt source freshness takes the newest loaded_at_field value in each table and measures how old it is, then compares that age to the thresholds. All three raw_shop tables share the 6h/12h pair, because 'A freshness and loaded_at_field property added to a source will be applied to all tables defined in that source.' The thresholds are strict: warn_after is a 'Duration ... after which dbt raises a warning if the most recent available data is older than this threshold', and error_after is a 'Duration ... after which dbt fails the freshness check if the most recent available data is older than this threshold.' orders at 2 hours is older than neither threshold, so it reports pass. payments at 8 hours is older than warn_after (6 hours) but not older than error_after (12 hours), so dbt reports warn. shipments at 20 hours is older than error_after, so dbt reports error. raw_ref.regions has no freshness block on the table, on its source, or in the project, and 'If neither is provided, then dbt will not calculate freshness for the tables in this source' — no state is calculated for it, so the 30-day age is never evaluated.

WHY THE OTHERS ARE WRONG

Reports error; a table with no freshness configuration fails the check
No table matches, because a missing freshness configuration does not fail the check: 'If neither is provided, then dbt will not calculate freshness for the tables in this source', so raw_ref.regions gets no state rather than an error — and all three raw_shop tables do have thresholds, inherited from the source-level config.
Reports pass; dbt falls back to default thresholds
dbt ships no built-in freshness thresholds, so an unconfigured table cannot fall back to a pass; it is simply not evaluated. The documented way to opt a table out explicitly is to 'set freshness: null', which would be meaningless if defaults filled the gap.

Know whether implementing and maintaining external dependencies is actually costing you marks.

The free readiness check is weighted like the real exam across all seven topics, so it tells you where you stand on this topic relative to the rest — which is the only version of that question worth answering before you book.

Take the free readiness check20 questions · ~15 minutes · no card

The other six topics

Keep reading

SOURCES

Every source above was read in full and last checked on .