WHEN TO USE DELTA LIVE TABLES (DLT)

Delta Live Tables (DLT) is most useful when you want automated, reliable, and declarative ETL pipelines with built-in data quality and orchestration. Below are the primary scenarios where DLT is the best choice.

1. Continuous or Streaming Ingestion

2. Built-in Data Quality (Expectations)

3. Automatic Bronze → Silver → Gold Orchestration

4. Data Lineage, Monitoring, and Observability

5. Automatic Table Optimization

6. Managing SCD Type 2 with Minimal Code

7. Declarative Pipelines Instead of Manual Jobs

8. Teams That Prefer Low-Code / No-Ops Data Engineering

Summary

  1. Use DLT for streaming, continuous, or reliable incremental pipelines.
  2. Use DLT when you want built-in data quality, monitoring, and orchestration.
  3. Use DLT to simplify complex logic such as SCD Type 2.
  4. Use DLT when you want automatic optimization and fewer maintenance tasks.

Summary

Delta Live Tables (DLT) is Databricks' declarative ETL framework, renamed Lakeflow Declarative Pipelines in 2025 with the same Python and SQL concepts. Instead of writing jobs that read, transform, write and schedule each step, you declare the tables you want and the query that defines each one. The runtime builds the dependency graph, provisions compute, processes data incrementally, enforces data-quality rules, and maintains the tables. The list above summarises when that trade is worth making; the sections below show how it works and what the code looks like.

How a pipeline runs

A DLT Pipeline as a DAG Auto Loadercloud files bronze_eventsstreaming table silver_eventsexpectations gold_dailymaterialized view You declare each table and the query that produces it; DLT infers dependencies from the references and runs them in order, retrying failures and recording quality metrics in the event log.

A pipeline contains two kinds of datasets. A streaming table processes each input record once and is appended to incrementally; it is the natural choice for ingestion with Auto Loader or Kafka. A materialized view stores the result of a query and is refreshed to reflect its sources, recomputing incrementally where possible. When an update runs, DLT resolves references between datasets into a DAG, starts the upstream tables first, and records each step in an event log that also powers the lineage graph in the UI. Pipelines run in triggered mode (process available data then stop) or continuous mode (keep running for low latency).

Expectations

Expectation Actions on Bad Records Incoming rowchecked by rule @expectkeep, count @expect_or_dropdrop row @expect_or_failstop update Every action records pass/fail counts, so data quality is visible without extra code.

Expectations are named boolean constraints attached to a dataset. The default action keeps violating rows and counts them; expect_or_drop removes them; expect_or_fail stops the update so bad data never lands. Because results go to the event log, teams can chart quality over time and alert on regressions without a separate validation framework.

Worked example: bronze to gold with SCD2

import dlt
from pyspark.sql.functions import col, count, to_date

@dlt.table(comment="Raw JSON events from cloud storage")
def bronze_events():
    return (spark.readStream.format("cloudFiles")
            .option("cloudFiles.format", "json")
            .load("/Volumes/main/raw/events/"))

@dlt.table
@dlt.expect_or_drop("has_user", "user_id IS NOT NULL")
@dlt.expect("recent", "event_ts > '2020-01-01'")
def silver_events():
    return dlt.read_stream("bronze_events").withColumn("event_date", to_date(col("event_ts")))

@dlt.table(comment="Daily counts for dashboards")
def gold_daily():
    return dlt.read("silver_events").groupBy("event_date").agg(count("*").alias("events"))

# SCD Type 2 customer dimension from a CDC feed
dlt.create_streaming_table("dim_customer")
dlt.apply_changes(
    target="dim_customer", source="customers_cdc",
    keys=["customer_id"], sequence_by=col("updated_at"),
    stored_as_scd_type=2)
-- Equivalent SQL for the silver step
CREATE OR REFRESH STREAMING TABLE silver_events (
  CONSTRAINT has_user EXPECT (user_id IS NOT NULL) ON VIOLATION DROP ROW
) AS SELECT *, to_date(event_ts) AS event_date FROM STREAM(bronze_events);

The apply_changes call replaces a hand-written MERGE: it orders changes by updated_at, closes the previous version of a changed customer with an end timestamp, and inserts the new version.

DLT vs jobs and notebooks

AspectDLT pipelineNotebook + Workflows job
StyleDeclarativeImperative
DependenciesInferred from codeConfigured task by task
Data qualityBuilt-in expectationsCustom code
Incremental processingBuilt inManual checkpoints and MERGE
FlexibilityConstrained to pipeline semanticsAnything Spark can do
Best forStandard medallion ETL, CDCComplex custom logic, ML jobs

Pitfalls