The bottom line
Snowflake offers two ways to build incremental pipelines. Streams and tasks are the imperative approach: a stream tracks changes on a table, a task runs SQL on a schedule to process them, and you wire the dependencies yourself — maximum control, maximum maintenance. Dynamic tables are declarative: you define the target as a query and a target freshness (lag), and Snowflake works out how to refresh it incrementally and manages the dependency chain for you — far less to maintain, less fine-grained control. For most mid-market teams building standard transformation chains, dynamic tables are now the better default because they remove orchestration code. Keep streams and tasks for the cases that need custom per-change logic, non-SQL side effects, or precise control dynamic tables do not expose. Choose by how much control you genuinely need versus how much pipeline you want to maintain.
In This Article
Two Ways to Process Incrementally
Once a Snowflake estate grows past "reload everything nightly", you need incremental processing — transforming only what changed rather than recomputing from scratch. Snowflake gives two native ways to do it, and they sit at opposite ends of a control-versus-effort spectrum.
Streams and tasks are the long-standing, imperative approach: you track changes and write the processing logic and schedule yourself. Dynamic tables are the newer, declarative approach: you describe the result you want and the freshness you need, and Snowflake figures out the incremental refresh. Both produce incrementally-maintained tables; they differ entirely in how much of the machinery you own.
Choosing well is mostly about being honest about how much control you actually need, because control you do not use is just maintenance you pay for.
Streams and Tasks: Imperative Control
A stream is a change-tracking object on a table — it records the rows inserted, updated or deleted since you last consumed it. A task is a scheduled unit of SQL. Put them together and the pattern is: a stream captures what changed, a task runs on a schedule to read the stream and apply the change to a downstream table, and you chain tasks together to build a multi-step pipeline.
The strength is precise control. You decide exactly what SQL runs on each change, how tasks depend on each other, when they fire, and what custom logic — conditional handling, calls to procedures, bespoke merge behaviour — happens at each step. For a pipeline with genuinely custom per-change requirements, that control is the point.
The cost is that you own all of it. The stream consumption, the task schedules, the dependency chain, the error handling, the "did this task actually run and succeed?" monitoring — that is your code and your operational burden. For a standard transformation chain with no special logic, it is a lot of machinery to maintain for a result that is, in the end, just "keep this table up to date from those".
Streams and tasks give precise control over per-change logic and scheduling — and you own all of it: the consumption, the dependency chain, the error handling, the monitoring.
Dynamic Tables: Declarative Lag
A dynamic table flips the model. Instead of writing how to process changes, you define the target table as a SQL query over its sources and set a target lag — how fresh it must be, say "within 5 minutes" or "within an hour". Snowflake then takes responsibility for refreshing it incrementally to meet that lag, and for working out and managing the dependency order when dynamic tables build on each other.
The effect is that an entire transformation chain becomes a set of declarative definitions with freshness targets, and the orchestration — what to refresh, when, in what order — disappears into the platform. There are no task schedules to wire, no streams to consume, no dependency graph to hand-maintain. You describe the shape of the result and the freshness; Snowflake keeps it true.
That is a large reduction in the code and operational surface of a pipeline. For the common case — a medallion-style chain of transformations where each layer is "this query over the layer below, kept reasonably fresh" — dynamic tables express exactly that intent with none of the plumbing.
The Control-Versus-Maintenance Trade
The trade is straightforward once stated. Dynamic tables give you far less to maintain and far less fine-grained control; streams and tasks give you full control and full maintenance. Neither is better in the abstract — the right choice depends on whether your pipeline actually needs the control that tasks provide.
Most transformation pipelines do not. They are chains of SQL that need to stay reasonably current, with no custom per-change logic and no non-SQL side effects — which is precisely what dynamic tables are for. Using streams and tasks there means writing and operating orchestration code to achieve something a declarative definition would handle for free.
The pipelines that do need tasks are the ones with requirements dynamic tables do not expose: custom logic that must run per change, side effects beyond maintaining a table (calling a procedure, triggering an external action), or precise scheduling and control that the lag abstraction does not give you. For those, the control is worth the maintenance. The mistake is paying the maintenance everywhere out of habit.
Control you do not use is maintenance you pay for. Most transformation chains just need to stay fresh — exactly what dynamic tables do declaratively — so keep streams and tasks for genuinely custom per-change logic.
So What — the Default and the Exceptions
For a mid-market team building standard incremental transformation chains on Snowflake, dynamic tables are now the sensible default: define each table as a query with a target lag and let the platform own the orchestration, removing a whole category of pipeline code and the operational load that comes with it. Reach for streams and tasks as the exception — when a step genuinely needs custom per-change logic, non-SQL side effects, or control the lag model does not provide.
The question to ask of any incremental step is simply: does this need bespoke processing, or does it just need to stay fresh? If it only needs to stay fresh, a dynamic table says so in one definition. If it needs bespoke processing, that is what streams and tasks are for. Matching the pattern to that answer — rather than defaulting to the imperative approach because it is familiar — is what keeps a Snowflake estate lean. We build Microsoft-first by default and apply the same principle on Snowflake where a client runs there: declare intent where you can, keep imperative control only where the work demands it.
Does this step need bespoke processing, or just to stay fresh? Fresh → a dynamic table in one definition. Bespoke → streams and tasks. Match the pattern to the answer, not to habit.
If your Snowflake pipelines have grown more orchestration code than transformation logic, the incremental pattern is worth revisiting. 30 minutes with Amit on where dynamic tables simplify your estate and where streams and tasks still earn their place. No slides, no pitch deck, no obligation to proceed.
Free Assessment
Where does your operation sit on the data maturity curve?
8 questions. 3 minutes. You get a scored breakdown across data infrastructure, analytics readiness, and automation potential — with a specific next step for your industry.