A dynamic table is a table defined by a query. Snowflake keeps it up to date for you, to within a freshness target called TARGET_LAG. You describe what you want, and Snowflake works out how to refresh it.
Example
CREATE DYNAMIC TABLE daily_revenue TARGET_LAG = '15 minutes' WAREHOUSE = etl_wh AS SELECT order_date, region, SUM(amount) AS revenue FROM core.orders GROUP BY order_date, region;
Snowflake will refresh it so that the data is no more than about 15 minutes behind the source. Where it can, it does an incremental refresh, processing only the changes since the last time. If the query uses something that cannot be done incrementally, it falls back to a full refresh, which recomputes everything and costs more. You can check which mode a table uses.
Chains
Dynamic tables can read from other dynamic tables. Snowflake works out the dependency graph, and refreshes them in the right order, so a chain of three or four steps is simple to declare.
Compared with streams and tasks
With streams and tasks, you write the stream, the task, the MERGE logic, the error handling, and you decide the schedule. You get full control: custom logic, side effects, handling deletes your own way. With a dynamic table, you write only the SELECT. It is much shorter and has fewer places for bugs. The cost is less control. You cannot run arbitrary steps, and you depend on Snowflake's refresh decisions and limits on which SQL can be refreshed incrementally.
Compared with materialized views
A materialized view is limited to a single table without joins, and is meant to speed up queries. A dynamic table supports joins and more SQL, and is meant to build pipelines.
How to choose
Use dynamic tables for transformation layers where "keep this table fresh to within N minutes" is the real requirement. Use streams and tasks when you need custom steps, exact timing, or logic that a single query cannot express. Watch the cost: a very low target lag means frequent refreshes.