DEV Community

Cover image for Hex vs Mode vs Sigma vs Deepnote: SQL-First Notebooks Compared
Gowtham Potureddi
Gowtham Potureddi

Posted on

Hex vs Mode vs Sigma vs Deepnote: SQL-First Notebooks Compared

hex vs mode is the single biggest analytics-notebook procurement call a data team makes in 2026 — and the one most teams get wrong on the first try because they treat all four vendors (Hex, Mode, Sigma, Deepnote) as "notebooks" instead of as fundamentally different centres of gravity. Hex is a reactive dependency-graph notebook. Mode is a SQL editor plus a scheduled-report engine. Sigma is a spreadsheet that compiles to warehouse SQL. Deepnote is Jupyter with multiplayer editing. Same category on the pricing page; wildly different mental models the moment you open the tool.

This guide is the senior-DE comparison you wished existed the first time an interviewer asked "when do you reach for a sql notebook vs a dashboard vs a dbt model?" or "compare deepnote vs hex for a mixed SQL-plus-Python team." It walks through Hex's reactive cell graph and publish-to-app (Snowflake / BigQuery / Databricks connectors, dbt semantic-layer integration, Hex Magic AI), Mode's SQL-editor + Helix in-memory engine + Datasets abstraction (the acquisition by ThoughtSpot in 2023 changed the roadmap), Sigma's spreadsheet-first pushdown model (every formula compiles to warehouse SQL, input tables write back), and Deepnote's Jupyter-compatible multiplayer notebook (real-time editing, GitHub sync, environment management via requirements.txt or Dockerfile). Each section pairs a teaching block with a Solution-Tail interview answer — code, a step-by-step trace, an output table, then a concept-by-concept breakdown of why it works.

PipeCode blog header for Hex vs Mode vs Sigma vs Deepnote — bold white headline 'SQL-First Notebooks' with subtitle 'Reactive, Pushdown, Publish, Collaborate' and four glyph medallions on a compass wheel around a central purple pick-one seal, on a dark gradient with purple, green, orange, and blue accents and a small pipecode.ai attribution.

When you want hands-on reps immediately after reading, drill the SQL practice library →, rehearse on aggregation problems →, and sharpen the joins axis with the SQL joins drills →.


On this page


1. Why the SQL notebook category matters in 2026

The collaborative analyst notebook has replaced the dashboard as the default surface for exploratory analytics

The one-sentence invariant: a SQL notebook is a single canvas where a SQL cell, a Python cell, a chart, and a shareable dashboard live inside the same document — the collaborative unit that has quietly replaced the classic BI dashboard for exploratory analytics. Once you accept that the notebook is the authoring surface (not the delivery surface), the entire hex vs mode interview surface collapses to "which authoring surface fits my team's centre of gravity?"

Three axes interviewers actually probe.

  • Reactive vs sequential execution. A reactive notebook (Hex) builds a dependency graph across cells; changing one cell auto-reruns downstream cells. A sequential notebook (Mode, Deepnote, Jupyter) reruns from top-to-bottom in order. Reactive is safer for stakeholder-facing apps; sequential is closer to the analyst's mental model.
  • Spreadsheet vs notebook UX. Sigma is a spreadsheet — the interaction model is pivot-table / formula-bar / cell-selection. Hex, Mode, and Deepnote are notebooks — the interaction model is cell-below / code-first / markdown-headings. The choice picks your target user: analyst-plus-exec vs analyst-plus-DE.
  • Publish surface. Every vendor eventually lets you publish the working document as a stakeholder-facing artifact. Hex publishes notebooks as interactive apps; Mode publishes as scheduled reports; Sigma publishes as dashboards / workbook pages; Deepnote publishes as Streamlit-style apps or shared notebook links. Same word, different UX.

The DSL split — same category, different mental model.

  • Hex — reactive notebook. Cells form a DAG. SQL cells expose their result as a Pandas dataframe variable to downstream Python cells and vice versa. publish-to-app is a first-class artifact type.
  • Mode — SQL editor first, Python second, Report third. Queries live in a Datasets library (reusable, parameterised). Helix (in-memory engine) reruns transformations without re-hitting the warehouse.
  • Sigma — no notebook at all. A spreadsheet grid on top of the warehouse. Every formula compiles to SQL and pushes down. Input tables let non-technical users write data back.
  • Deepnote — Jupyter kernel + multiplayer editor. Block-based cells (SQL / Python / markdown / chart) with GitHub sync and Dockerfile-managed environments.

The 2026 reality — what changed since 2023.

  • Mode was acquired by ThoughtSpot in 2023. ThoughtSpot's search-driven analytics layer now sits on top of Mode; the roadmap is bifurcated between "legacy Mode reports" and "ThoughtSpot search integration".
  • Hex added dbt semantic layer support — SQL cells can pull metrics defined in your dbt project directly, so the notebook and the warehouse share the same metric definitions.
  • Sigma shipped input tables + write-back — the spreadsheet is no longer read-only; users type into cells and the changes land in the warehouse as new rows.
  • Deepnote added AI Copilot across SQL, Python, and markdown cells, plus block-based cells that compile to a Streamlit-style app for stakeholders.
  • Hex Magic, Mode Visual Explorer, Sigma AI all shipped GA in 2024–2025. Every vendor now has an AI story; the differentiators are cell-graph reasoning (Hex), search-based exploration (Mode + ThoughtSpot), and formula-generation from natural language (Sigma).

What interviewers listen for.

  • Do you say "a notebook is the authoring surface, not the delivery surface" in the first sentence? — senior signal.
  • Do you describe the axes as "reactive vs sequential, spreadsheet vs notebook, publish-to-app vs schedule-report" unprompted? — required answer.
  • Do you push back on "they're all just notebooks" with "different centres of gravity for different users"? — senior signal.
  • Do you mention "pushdown SQL is the difference between a 2s query and a 2min OOM" for Sigma vs an in-memory notebook? — senior signal.

Worked example — same daily-revenue query, four tools

Detailed explanation. The classic analyst Hello World — compute yesterday's revenue by country from an orders table — looks superficially similar in all four tools. The DSL reads almost identically. The runtime is wildly different: Hex builds a reactive cell graph; Mode runs a sequential SQL editor + Python; Sigma pushes down a spreadsheet formula; Deepnote runs a Jupyter cell. Only Hex re-runs the downstream chart automatically the moment the SQL cell changes.

Question. Show the same "revenue by country yesterday" analysis in Hex, Mode, Sigma, and Deepnote. Highlight the DSL, the execution model (reactive vs sequential vs pushdown), and where the aggregation happens.

Input.

order_id country amount created_at
1 US 50 2026-07-16 09:00
2 DE 30 2026-07-16 10:00
3 US 75 2026-07-16 11:00
4 FR 40 2026-07-16 12:00
5 DE 20 2026-07-16 14:00

Code.

-- Hex — SQL cell in a reactive notebook (Snowflake data source)
SELECT country, SUM(amount) AS revenue
FROM orders
WHERE created_at::DATE = CURRENT_DATE - 1
GROUP BY country
ORDER BY revenue DESC;

-- Downstream Python cell references the SQL cell's dataframe as `sql_1`
-- and re-runs automatically when the SQL above changes.
Enter fullscreen mode Exit fullscreen mode
-- Mode — SQL editor tab; Python notebook tab is separate
SELECT country, SUM(amount) AS revenue
FROM orders
WHERE created_at::DATE = CURRENT_DATE - 1
GROUP BY country
ORDER BY revenue DESC;

-- Result lands in Helix in-memory; Python tab reruns manually
Enter fullscreen mode Exit fullscreen mode
-- Sigma — no SQL is written by the user
-- Drag `orders` table into a workbook page
-- Add a pivot with GROUP-BY country and SUM(amount)
-- Sigma compiles that spreadsheet pivot into:
--   SELECT country, SUM(amount) FROM orders
--   WHERE created_at::DATE = CURRENT_DATE - 1
--   GROUP BY country
-- and pushes it down to Snowflake.
Enter fullscreen mode Exit fullscreen mode
# Deepnote — SQL block + Python block (Jupyter kernel)
%%sql
SELECT country, SUM(amount) AS revenue
FROM orders
WHERE created_at::DATE = CURRENT_DATE - 1
GROUP BY country;

# Second cell — plot manually after clicking Run
import plotly.express as px
px.bar(revenue_df, x="country", y="revenue")
Enter fullscreen mode Exit fullscreen mode

Step-by-step explanation.

  1. All four tools push the aggregation down to the warehouse for the SQL step — no data movement, no in-memory GROUP BY. The warehouse returns three rows.
  2. In Hex, the SQL cell's result is exposed as a Pandas dataframe variable (sql_1) to every downstream Python and chart cell. Editing the SQL auto-reruns the chart cell — reactive execution.
  3. In Mode, the SQL editor is a separate tab from the Python notebook. Rerunning the SQL updates the Helix in-memory copy; the Python tab must be reloaded manually to pick up the new data.
  4. In Sigma, the user never writes SQL. Dragging columns into a pivot, choosing GROUP BY country + SUM(amount) is the interaction; Sigma's compiler translates that into the SQL above and sends it to the warehouse.
  5. In Deepnote, a %%sql cell binds the query result to a Python variable. The next cell reads that variable but does not re-run until the user clicks Run — sequential execution, not reactive.

Output.

country revenue
US 125
DE 50
FR 40

Rule of thumb. Same result, four execution models. Pick the model that fits how your team edits: reactive if stakeholders view the output (safer), sequential if analysts iterate line-by-line (closer to the mental model), spreadsheet if non-technical exec users need to build (approachable), Jupyter if the team already lives in Python (zero learning curve).

Worked example — notebook vs dashboard vs dbt model decision

Detailed explanation. A common interview question — "your PM asks for a daily active user chart; do you build it as a notebook, a dashboard, or a dbt model?" The answer is the cleanest demonstration of the authoring vs delivery split, and it decides which of the four tools even applies.

Question. Given a request for a "daily active users chart with country and device filters", walk through the notebook vs dashboard vs dbt-model decision, and pick the right tool for each layer.

Input.

Layer Purpose Frequency of change
Metric definition one dau calculation, agreed by every team monthly
Dashboard filterable chart for stakeholders quarterly (layout)
Notebook ad-hoc analysis + one-off queries weekly (analyst-driven)

Code.

# dbt — the metric definition, shared source of truth
# models/marts/metrics/dau.yml
version: 2
metrics:
  - name: dau
    label: Daily Active Users
    model: ref('fact_events')
    calculation_method: count_distinct
    expression: user_id
    timestamp: event_ts
    time_grains: [day]
    dimensions:
      - country
      - device
Enter fullscreen mode Exit fullscreen mode
# Notebook — Hex cell wired to the dbt semantic layer
{{ metric('dau', grain='day', dimensions=['country', 'device']) }}

# The notebook does NOT redefine `dau` — it queries the metric.
Enter fullscreen mode Exit fullscreen mode
# Dashboard — the same metric surfaced via Sigma workbook
# Drag dau_metric into a workbook page → group by country → add device filter
# Sigma compiles the pivot into a query against the metric view.
Enter fullscreen mode Exit fullscreen mode

Step-by-step explanation.

  1. The metric definition lives in dbt — one place, versioned, code-reviewed. Every downstream tool queries the metric by name, never redefines it.
  2. The dashboard is a delivery surface — a stable, filterable, drill-downable view for stakeholders. Sigma (spreadsheet UX) or Hex published-as-app both fit; Sigma is the better answer for exec users.
  3. The notebook is an authoring surface — the analyst iterates on new questions, joins ad-hoc tables, and either promotes a chart to the dashboard or writes a Slack summary.
  4. Confusing the three is the #1 mistake: teams build DAU in the notebook, get different numbers than the dashboard, and re-define the metric in every downstream tool. Metric drift follows within weeks.
  5. The correct pattern in 2026 — dbt owns the metric; the notebook (Hex / Mode / Deepnote) queries the metric for exploration; the dashboard (Sigma / Hex-app) surfaces the metric for consumption.

Output.

Layer Tool Why
Metric dbt version-controlled, code-reviewed, single source of truth
Notebook Hex / Deepnote reactive graph + AI, analyst iteration
Dashboard Sigma / Hex-app pushdown SQL, filterable, exec-friendly
Report (email) Mode scheduled + subscription-driven

Rule of thumb. dbt owns the what, the notebook owns the why, the dashboard owns the look, and the scheduled report owns the when. Do not mix layers; do not redefine metrics.

Worked example — pushdown vs in-memory aggregation on a billion-row table

Detailed explanation. An analyst wants to compute a rolling 30-day revenue by country from a 1B-row orders table. The tools split cleanly: Sigma and Hex-with-SQL-cells push the aggregation down to Snowflake (millions of rows scanned, 3 rows returned). Deepnote / Mode with a Python cell that SELECT * first will OOM the kernel long before the aggregation runs.

Question. Given a 1B-row orders table on Snowflake, show the pushdown SQL vs the naive Python approach, explain the memory ceiling, and pick the tool that respects the ceiling.

Input.

Table Row count Size on warehouse Memory per notebook kernel
orders 1,000,000,000 ~200 GB 8 GB (typical)

Code.

-- Pushdown — Sigma / Hex SQL cell / Mode SQL editor
-- Aggregation happens in Snowflake; 3 rows returned to the notebook
SELECT country,
       SUM(amount) AS revenue_30d
FROM   orders
WHERE  created_at >= CURRENT_DATE - 30
GROUP  BY country;
Enter fullscreen mode Exit fullscreen mode
# Naive — Deepnote / Mode Python cell that pulls everything first
import pandas as pd
df = pd.read_sql("SELECT * FROM orders WHERE created_at >= CURRENT_DATE - 30", conn)
# df is ~30M rows even for one month — kills the 8 GB kernel
result = df.groupby("country")["amount"].sum()
Enter fullscreen mode Exit fullscreen mode

Step-by-step explanation.

  1. The pushdown SQL query scans 30M rows inside Snowflake using its columnar engine, groups by country, and returns a 3-row result set. Data transferred to the notebook: negligible.
  2. The naive Python approach pulls all 30M rows over the wire into the notebook kernel's memory. At ~200 bytes per row, that is ~6 GB — the kernel is at 75% memory before the groupby even starts.
  3. On the full 1B-row scan (no date filter), pushdown returns instantly; the Python approach OOMs and kills the notebook, forcing the analyst to add a LIMIT and re-run — the classic beginner mistake.
  4. Sigma cannot make this mistake by design — every operation is pushdown. Hex exposes SQL cells natively and encourages pushdown. Mode has a SQL-first editor. Deepnote exposes Python cells first — the notebook DSL implicitly encourages SELECT *-then-Python patterns that OOM.
  5. The senior-DE rule: if the source table exceeds the notebook kernel's memory, the aggregation must happen in the warehouse. Every tool supports this; only Sigma enforces it.

Output.

Approach Rows over wire Notebook memory Runtime
Pushdown SQL 3 100 KB ~2 s
Naive Python SELECT * 30M 6 GB ~90 s
Naive Python SELECT * (no filter) 1B OOM crash

Rule of thumb. For source tables above ~10M rows, always push the aggregation down as SQL. Save Python for the result set, not the raw table. The tool that fits your team is the one that makes the pushdown pattern the default — not the exception.

Senior interview question on notebook category selection

A senior interviewer often opens with: "Walk me through how you'd pick a SQL notebook for a mixed data-engineering-plus-analytics team of 20. What are the three or four questions you ask, in order, and what answer to each one pushes you toward Hex, Mode, Sigma, or Deepnote?"

Solution Using a 4-question notebook decision framework

Decision framework — SQL notebook selection

1. Who is the primary author?
   - Analysts + DEs, code-first        → Hex / Deepnote
   - Analysts + execs, spreadsheet UX  → Sigma
   - Analysts alone, SQL-heavy reports → Mode

2. What is the primary delivery surface?
   - Interactive app for stakeholders  → Hex publish-to-app
   - Scheduled email report            → Mode Reports
   - Filterable exec dashboard         → Sigma workbook
   - Shared notebook link              → Deepnote

3. Does the team need reactive execution?
   - Yes (stakeholder-facing safety)   → Hex
   - No (analyst iteration)            → Mode / Deepnote / Sigma

4. Where does the metric layer live?
   - dbt semantic layer                → Hex (first-class support)
   - Warehouse views                   → Sigma / Mode / Deepnote
   - Undefined                         → fix that before picking a tool
Enter fullscreen mode Exit fullscreen mode

Step-by-step trace.

Team Q1 author Q2 delivery Q3 reactive Q4 metric Picked
Growth team, exec-facing analyst+exec filterable dashboard no warehouse views Sigma
DE + analyst team, dbt-native analyst+DE interactive app yes dbt semantic Hex
Ops team, weekly digest analyst scheduled email no warehouse views Mode
Data science team, Python-first DS+DE notebook link no code-defined Deepnote

After the 4-question pass, the tool choice is usually unambiguous. The remaining 5% — where two tools work — defaults to whatever the team already operates.

Output:

Tool When it wins
Hex reactive graph, publish-to-app, dbt semantic-layer team
Mode SQL-first analyst team, scheduled reports, ThoughtSpot integration
Sigma spreadsheet UX, exec-facing dashboards, pushdown-only workloads
Deepnote Jupyter-compatible team, GitHub sync, DS-heavy workflow

Why this works — concept by concept:

  • Author vs delivery surface — the structural axis — every other consequence (reactive, semantic-layer, publish shape) follows from who edits and who consumes. Asking the audience question first short-circuits a lot of false dichotomies.
  • Reactive vs sequential — Hex is reactive by design; the other three are sequential (or spreadsheet). If a stakeholder will edit filters, reactive is safer; if an analyst iterates line-by-line, sequential is closer to the mental model.
  • Metric-layer discipline — a notebook that redefines DAU inside a SQL cell is a metric drift bug waiting to happen. Hex's dbt semantic layer integration eliminates the class; the other tools require warehouse views or discipline.
  • Publish surface is a product decision — publish-to-app (Hex), scheduled report (Mode), workbook dashboard (Sigma), shared notebook (Deepnote) are four different products even though the underlying notebook is the same category.
  • Cost — every tool bills per-user (analyst seats) plus per-viewer (stakeholder seats). Hex and Sigma trend enterprise; Mode is prosumer; Deepnote is developer-priced. Cluster TCO often dominates once you cross 20 seats.

SQL
Topic — sql
SQL notebook selection problems

Practice →

SQL Topic — aggregation Aggregation problems (SQL)

Practice →


2. Hex — collaborative reactive SQL + Python notebook

hex is a reactive cell-graph notebook — change one SQL cell, every downstream chart re-renders without a rerun click

The mental model in one line: a Hex project is a DAG of cells (SQL, Python, chart, input, markdown); editing a cell's output triggers automatic re-execution of every downstream cell in topological order, and any project can be published as an interactive app with input widgets driving the graph. Once you say "cells are nodes, edits are events, downstream re-runs are automatic," every hex vs mode interview question about Hex becomes a deduction from "reactive graph + first-class SQL + publish-to-app."

Visual diagram of the Hex reactive dependency graph — five notebook cells connected as a DAG with the top SQL cell marked 'changed' and three dependent cells re-running automatically, plus a publish-to-app side card showing notebook, app view, and dbt semantic layer icons; on a light PipeCode card.

The three core cell types.

  • SQL cell — a first-class native cell type. Connects to a data source (Snowflake, BigQuery, Databricks, Redshift, Postgres, MySQL). The result set is auto-bound to a Pandas dataframe variable (sql_1, sql_2, or a user-named alias).
  • Python cell — runs in a per-project kernel. Can read any upstream cell's output variable (SQL dataframe, Python variable, chart selection). Uses uv-backed package management.
  • Chart / input cell — chart cells render from a dataframe variable via a low-code chart builder or Vega-Lite. Input cells expose sliders, dropdowns, text boxes that become variables in the graph.

The reactive execution model.

  • Every cell declares its inputs implicitly. A Python cell that references sql_1.groupby(...) declares a dependency on the SQL cell whose output is sql_1.
  • Editing a cell invalidates its downstream subgraph. Hex reruns only the invalidated cells, in topological order, using cached results for everything else.
  • Cells are pure by convention. Side-effecting cells (writes, API calls) must be wrapped explicitly; otherwise, an accidental re-run could double-write.
  • Cache is per-cell + per-input-hash. If the SQL cell has already run with the same query and same connection state, Hex serves the cached dataframe.

Publish-to-app — the notebook is the app.

  • Any Hex project has an app view (built-in). The app view hides the cells and surfaces only the outputs (charts, tables, inputs) in a stakeholder-facing layout.
  • Input widgets (dropdowns, sliders) become URL query parameters — a stakeholder-shareable filtered view is just a URL.
  • Apps run on a separate kernel from the notebook editor. Editing the notebook does not disturb an app-view session.
  • Apps support secrets scoped to the app — you can drive a filter from a viewer's identity (row-level security via a Jinja filter on the SQL cell).

dbt semantic-layer integration.

  • Hex can connect to the dbt semantic layer (via dbt Cloud or dbt-mesh). SQL cells can invoke metrics defined in dbt with {{ metric('dau', grain='day') }}.
  • The metric definition lives in dbt (version-controlled); the notebook queries it. No metric drift between the dashboard and the notebook.
  • Semantic layer support unlocks compiled SQL preview in Hex — you see the generated SQL before it hits the warehouse.

Hex Magic — the AI layer.

  • Every cell has a magic prompt — natural-language instructions that generate SQL, Python, or chart code.
  • Hex Magic is cell-graph aware — it reads the schema of every upstream dataframe when generating downstream code. That awareness is why the completions are noticeably better than a plain LLM.
  • Prompt: "chart the top 10 countries by revenue" — Hex Magic reads sql_1's columns, picks the right dataframe, and generates a Vega-Lite bar chart cell.

Pricing model (2026).

  • Community — free, single user, unlimited projects. Great for evaluation.
  • Team — per-seat monthly, ~$50 per editor. Includes multiplayer editing, published apps.
  • Business / Enterprise — custom pricing, includes SSO, VPC deployment, row-level-security app secrets, dbt semantic layer integration, audit logging.

Common interview probes on Hex.

  • "How does Hex know a cell must re-run?" — it tracks variable references across cells and builds an implicit DAG. Any variable referenced in one cell that was assigned in another creates an edge.
  • "What is publish-to-app in Hex?" — the notebook's app view surfaces only outputs and inputs to a stakeholder; the same project is both the authoring surface and the delivery surface.
  • "When would Hex Magic pick the wrong dataframe?" — when multiple upstream cells produce dataframes with overlapping column names; Hex Magic uses the last-run cell as the default.
  • "How does Hex integrate with dbt?" — via the dbt semantic layer (dbt Cloud metric endpoint); SQL cells query metrics by name; compiled SQL is visible in the cell.

Worked example — reactive re-run of a downstream chart

Detailed explanation. A Hex project has three cells: an SQL cell that queries orders, a Python cell that reshapes the dataframe, and a chart cell that renders a bar chart. The analyst edits the SQL to add a WHERE country = 'US' filter. Every downstream cell re-runs automatically. This is the exact opposite of the Jupyter / Mode / Deepnote experience where the analyst must click Run on each cell in turn.

Question. Given a 3-cell Hex project, show the DSL, the dependency graph Hex infers, and what happens when the SQL cell changes.

Input.

Cell Type Output variable
1 SQL sql_1 (dataframe)
2 Python enriched (dataframe)
3 Chart (no variable; renders from enriched)

Code.

-- Cell 1 (SQL cell, auto-named sql_1)
SELECT country, amount, created_at
FROM   orders
WHERE  created_at >= CURRENT_DATE - 7;
Enter fullscreen mode Exit fullscreen mode
# Cell 2 (Python cell)
import pandas as pd
enriched = (
    sql_1
    .assign(day=lambda d: pd.to_datetime(d["created_at"]).dt.date)
    .groupby(["country", "day"], as_index=False)["amount"]
    .sum()
)
Enter fullscreen mode Exit fullscreen mode
# Cell 3 (Chart cell) — no code, low-code chart builder
X-axis:    day
Y-axis:    amount
Color-by:  country
Chart:     stacked bar
Enter fullscreen mode Exit fullscreen mode

Step-by-step explanation.

  1. On first run, Hex executes cells 1 → 2 → 3 in topological order. Cell 1 queries Snowflake; cell 2 reads sql_1; cell 3 reads enriched. All three are cached.
  2. The analyst edits cell 1 to add WHERE country = 'US'. Hex invalidates the cached result for cell 1 and marks cells 2 and 3 as stale (because they reference sql_1 and enriched respectively).
  3. Hex re-runs cell 1 with the new query. Cell 2 detects that sql_1 changed and re-runs (Python groupby executes in-notebook memory — small result set, ~200 ms). Cell 3 detects enriched changed and re-renders the chart.
  4. The stakeholder viewing the app version sees the chart update in ~1 second — no rerun click required.
  5. If cell 2 had a side effect (e.g. writing to a Slack webhook), the automatic re-run would be dangerous. Hex flags side-effecting cells explicitly; the analyst wraps them in an if input_run_button: guard.

Output (dependency graph Hex infers).

Cell Reads Writes Downstream
1 (SQL) sql_1 2, 3 (via 2)
2 (Python) sql_1 enriched 3
3 (Chart) enriched

Rule of thumb. Model every Hex project as a DAG of pure cells. Wrap any side-effecting code in an explicit input-button guard. The reactive execution model is Hex's superpower — do not fight it by putting mutations mid-chain.

Worked example — publish-to-app with input filters

Detailed explanation. The analyst wants to hand a stakeholder a self-service view of revenue-by-country with a country filter and a date-range slider. In Hex, that is not a rewrite — it is a two-input-cells-plus-publish workflow. The notebook becomes an app; the same variables that drive the notebook drive the app.

Question. Add a country dropdown and a date-range slider to the notebook above; parameterise the SQL cell to use them; publish as an app.

Input.

Widget Variable Bound to
dropdown country_input SQL cell WHERE clause
date range date_range SQL cell WHERE clause

Code.

# Cell A (Input cell — dropdown)
Type:     dropdown
Variable: country_input
Options:  [US, DE, FR, UK, JP]
Default:  US

# Cell B (Input cell — date range)
Type:     date_range
Variable: date_range
Default:  last 7 days
Enter fullscreen mode Exit fullscreen mode
-- Cell 1 (SQL cell — parameterised via Jinja)
SELECT country, amount, created_at
FROM   orders
WHERE  country = {{ country_input }}
  AND  created_at BETWEEN {{ date_range[0] }} AND {{ date_range[1] }};
Enter fullscreen mode Exit fullscreen mode
# Publish → App view
# The stakeholder sees only Cell A, Cell B, and the chart (Cell 3).
# Changing the dropdown or slider fires the reactive re-run chain.
Enter fullscreen mode Exit fullscreen mode

Step-by-step explanation.

  1. Input cells expose their value as project-scoped variables. Referencing them in the SQL cell with Jinja ({{ country_input }}) automatically registers the dependency in the graph.
  2. When the stakeholder picks a different country from the dropdown, Hex invalidates the SQL cell, re-runs it, re-runs cell 2, re-renders the chart.
  3. The app view is a separate URL. The notebook editor and the app view can be open concurrently; edits to the notebook do not disturb the app view session.
  4. Hex writes the current input values into the URL as query params — a shareable link is just ?country_input=US&date_range=2026-07-10,2026-07-17.
  5. Publishing to app takes a single click; unpublishing is instant. There is no separate app-build step.

Output (app-view layout).

Element Source cell
Country dropdown Cell A (input)
Date-range slider Cell B (input)
Bar chart Cell 3 (chart)
Hidden: SQL + Python cells Cells 1, 2

Rule of thumb. For any notebook that a stakeholder will read, add input cells for the dimensions they care about and publish to app. The reactive graph and the input-widget URL make the shareable link a full self-service tool with no code hand-off.

Worked example — dbt semantic layer metric inside a Hex SQL cell

Detailed explanation. The team defines dau as a metric in dbt. A Hex SQL cell queries the metric by name via the semantic layer; the notebook and the dashboard now share the same metric definition. Change dbt, both surfaces update. This is the interview-worthy pattern for hex vs mode in 2026.

Question. Query the dau metric from a Hex SQL cell via the dbt semantic layer, add a country dimension, and render a time-series chart.

Input.

Layer Owns
dbt metric definition, dimensions, timestamp column
Hex SQL cell + chart cell

Code.

# dbt — the metric (version-controlled, in the dbt repo)
metrics:
  - name: dau
    label: Daily Active Users
    model: ref('fact_events')
    calculation_method: count_distinct
    expression: user_id
    timestamp: event_ts
    time_grains: [day]
    dimensions: [country, device]
Enter fullscreen mode Exit fullscreen mode
-- Hex SQL cell — query the metric via the semantic layer
SELECT {{ metric('dau', grain='day', dimensions=['country'], start_date='2026-01-01') }};

-- The semantic layer expands this into:
--   SELECT DATE_TRUNC('day', event_ts) AS day,
--          country,
--          COUNT(DISTINCT user_id) AS dau
--   FROM   fact_events
--   WHERE  event_ts >= '2026-01-01'
--   GROUP  BY 1, 2
Enter fullscreen mode Exit fullscreen mode

Step-by-step explanation.

  1. The metric() Jinja function is a Hex-provided macro that talks to the dbt Cloud semantic-layer API. Hex sends the metric name and dimensions; dbt returns the compiled SQL.
  2. Hex substitutes the compiled SQL into the cell and executes it against the connected warehouse. The cell's dataframe is sql_1 as usual.
  3. If someone in the dbt repo changes the dau definition (say, adds a filter for is_signed_up = true), the next run of the Hex cell picks up the new SQL — no notebook edit required.
  4. The compiled SQL is visible in the Hex cell's "Compiled" tab, so an interviewer can literally see what will hit the warehouse.
  5. This is the pattern that eliminates metric drift between the notebook (Hex) and the dashboard (Sigma / Hex-app / Looker) — as long as every surface goes through the semantic layer.

Output.

day country dau
2026-07-15 US 12,340
2026-07-15 DE 3,120
2026-07-15 FR 1,880

Rule of thumb. Never redefine a metric inside a Hex SQL cell if the metric already exists in dbt. Query it via metric() — you get consistency, version control, and a compiled-SQL preview for free.

Senior interview question on Hex reactive execution

A senior interviewer might ask: "Your Hex notebook has 30 cells. A stakeholder complains that changing a filter triggers a 45-second re-run. How do you diagnose which cell is slow and reduce the re-run cost — without changing the notebook structure?"

Solution Using cell-graph profiling + cache invalidation strategy

# Hex — cell-graph profiling checklist
# 1) Open the Run Timeline in the Hex sidebar — every cell reports duration.
# 2) Look for the top-3 slowest downstream cells (usually SQL cells that
#    re-hit the warehouse on every filter change).
# 3) Move slow SQL cells UPSTREAM of the input widgets — they will not
#    invalidate on filter change, only on data source change.

# Example — before (input drives everything)
sql_orders  = query_orders(country_input, date_range)          # ~10s / warehouse hit
sql_users   = query_users(country_input, date_range)           # ~8s / warehouse hit
sql_events  = query_events(country_input, date_range)          # ~15s / warehouse hit
merged      = merge(sql_orders, sql_users, sql_events)         # ~1s / in-notebook
filtered    = filter_by_input(merged, country_input, date_range)
chart_data  = compute_chart(filtered)

# Example — after (fetch once, filter in-notebook)
sql_orders  = query_orders_last_90d()                          # ~10s / cached until 24h TTL
sql_users   = query_users_active()                             # ~8s  / cached until 24h TTL
sql_events  = query_events_last_90d()                          # ~15s / cached until 24h TTL
merged      = merge(sql_orders, sql_users, sql_events)         # ~1s  / cached
filtered    = filter_by_input(merged, country_input, date_range)  # ~200ms / re-runs on input
chart_data  = compute_chart(filtered)                          # ~50ms / re-runs on input
Enter fullscreen mode Exit fullscreen mode

Step-by-step trace.

Step Before After
SQL cells re-run on filter change 3 (all of them) 0 (they don't reference inputs)
Warehouse round-trips per filter change 3 0
In-notebook filter cost ~200 ms ~200 ms
Total filter-change latency ~35 s ~250 ms
Cache invalidated by any input change data source refresh (24h TTL)

After the refactor, the SQL cells no longer sit downstream of the input widgets. Changing a filter re-runs only the in-notebook filter and chart cells — a 140x speed-up on the same DAG.

Output:

Metric Before After
Filter change latency 45 s ~300 ms
Warehouse cost per filter change 3 queries × 200 GB scan 0
Notebook memory 12 MB 200 MB (holds 90d dataset)
Cache hit rate ~5% ~95%

Why this works — concept by concept:

  • Fetch once, filter many — the classic pattern for reactive notebooks. Pay the warehouse cost once (upstream, cached), pay the filter cost cheaply in the notebook every time the input changes.
  • Input widgets are graph edges — an input widget referenced by a cell puts that cell downstream. The refactor removes the reference from the SQL cell (moves it to the filter step), which removes the edge.
  • Cache TTL vs input change — Hex's cache is keyed by cell inputs; the 24-hour TTL fires only when the upstream data source changes, not on every filter tweak.
  • Trade-off: notebook memory grows — the 90-day dataset now lives in the notebook kernel. For datasets > ~1 GB, this pattern fails — fall back to pushdown SQL with per-input parameters and accept the round-trip.
  • Cost — the refactor is O(1) engineering effort but O(N) warehouse cost savings per filter change; over a week of stakeholder use, the pattern often pays for a month of Hex seat cost.

SQL
Topic — sql
Hex reactive-notebook problems

Practice →

SQL Topic — window-functions Window function problems (SQL)

Practice →


3. Mode — SQL editor + Python + report

mode analytics is a SQL editor plus a report engine — Helix in-memory reruns let you iterate without re-hitting the warehouse

The mental model in one line: a Mode project is a Report — a container that holds one or more SQL Datasets (parameterised queries), an in-memory Helix copy of every result, a Python or R notebook that transforms the data further, and a rendered report with chart + text blocks that ships as a scheduled email. Once you say "SQL first, Helix in the middle, report at the end," every mode analytics interview question becomes a deduction from that three-stage flow.

Visual diagram of the Mode SQL editor + Helix engine + scheduled report — a SQL editor card feeding a green Helix in-memory cylinder, then a fanned deck of scheduled report pages with an envelope glyph, plus a Datasets side card with three parameterised query chips; on a light PipeCode card.

The three-stage Mode flow.

  • SQL editor — the primary interaction. A tab with a query, a connection to Snowflake / BigQuery / Redshift / Postgres, and a result grid. Multiple SQL tabs per report; each is a Dataset.
  • Helix engine — Mode's in-memory columnar cache. After a SQL query runs against the warehouse, Helix holds the result set in memory; subsequent Python transforms, chart reruns, and filter tweaks read from Helix instead of the warehouse.
  • Python / R notebook — a Jupyter-style notebook wired to the Datasets. Each Dataset shows up as a Pandas dataframe (datasets["orders"]). The notebook re-runs sequentially (top-to-bottom), like Jupyter, unlike Hex.
  • Report — the rendered chart+text output. Publishable as a permalink, embeddable, and schedulable as an email digest to any list.

Datasets — parameterised, reusable queries.

  • A Dataset is a SQL query + a parameter list + a description. Named, reusable across reports.
  • Parameters expose runtime variables: {{ start_date }}, {{ end_date }}, {{ country }}. Values are chosen by the report viewer via URL query params or a schedule config.
  • Datasets are a governance win — one query definition per business concept; every report references the Dataset by name.
  • ThoughtSpot's post-acquisition roadmap layers a semantic layer on top of Datasets, so metrics stay consistent across every report.

The Helix engine — reruns without warehouse cost.

  • After the first execution, the result set is cached in Helix (in-memory, columnar, per-report-run).
  • Python cells transform the Helix copy without hitting the warehouse. Chart cells render from Helix. Filter drags on the report re-query Helix.
  • The Helix cache is invalidated when the SQL Dataset is edited or on a scheduled refresh (nightly by default). Explicit Refresh button reruns everything against the warehouse.
  • Helix has a size ceiling — typically ~10M rows per Dataset. Larger result sets require pushdown filters in the SQL itself.

Reports — scheduled email, permalink, embed.

  • A Report is the deliverable. It holds one or more charts, text blocks, and a hidden Python notebook.
  • Schedule. Cron-based: "every Monday at 9 AM, email to sales-leadership@". Attaches the rendered report as HTML + a downloadable CSV.
  • Permalink. Anyone in the workspace can view. Anonymised links available for public sharing.
  • Embed. iframe-embeddable, so a Mode report can live inside a Notion doc, a Confluence page, or a custom dashboard.

Mode Visual Explorer — the AI layer.

  • A natural-language chart builder. "Show me the top 10 countries by revenue" generates a chart from the current Datasets without writing SQL or Python.
  • Search-driven exploration is where the ThoughtSpot heritage shines — after the 2023 acquisition, ThoughtSpot's "search analytics" UX is being folded into Mode.
  • Visual Explorer is best for the exec who wants a chart but never writes SQL — it fills the same niche Hex Magic fills for the analyst who writes SQL and Python.

Pricing model (2026).

  • Studio (free) — single user, unlimited public reports. Legacy free tier.
  • Business — per-seat pricing, ~$50 per creator, viewer seats separate. Includes scheduled reports, Datasets, Helix.
  • Enterprise — custom pricing, includes SSO, VPC, ThoughtSpot integration, audit logging.

Common interview probes on Mode.

  • "What is Helix and why does it matter?" — the in-memory engine that caches SQL results so Python / chart / filter interactions do not re-hit the warehouse. It is Mode's answer to the "each filter is a $2 Snowflake query" pain.
  • "What is a Dataset?" — a named, parameterised SQL query; reusable across reports; the governance primitive that keeps queries DRY.
  • "How does Mode's Python notebook differ from Hex's?" — Mode is sequential (top-to-bottom, click Run), Hex is reactive (DAG, auto re-run). Mode's notebook is a tab; Hex's cells are the canvas.
  • "What changed after the ThoughtSpot acquisition?" — search-driven analytics on top, unified semantic layer across Mode + ThoughtSpot, tighter integration for enterprise customers.

Worked example — SQL dataset + parameterised report

Detailed explanation. A revenue-by-country report needs to work for any date range and any target country. The classic pattern: one Dataset with two parameters, referenced from a Python + chart + text report. The scheduled email pushes it to sales leadership every Monday.

Question. Build a Mode Dataset for daily revenue by country with start_date and end_date parameters; render a bar chart in the report; schedule an email.

Input.

Parameter Type Default
start_date date today - 7
end_date date today

Code.

-- Mode Dataset — "daily_revenue_by_country"
-- Two parameters, resolved at run time from the report URL / schedule
SELECT country,
       DATE_TRUNC('day', created_at)::DATE AS day,
       SUM(amount) AS revenue
FROM   orders
WHERE  created_at >= {{ start_date }}
  AND  created_at <  {{ end_date }}
GROUP  BY 1, 2
ORDER  BY day, revenue DESC;
Enter fullscreen mode Exit fullscreen mode
# Mode Python cell — read the Dataset into a Pandas dataframe
import pandas as pd
rev = datasets["daily_revenue_by_country"]

top_countries = (
    rev.groupby("country", as_index=False)["revenue"]
       .sum()
       .nlargest(10, "revenue")
)

# The chart cell renders from `top_countries`
Enter fullscreen mode Exit fullscreen mode
# Report → Chart cell
Data:      top_countries
X-axis:    country
Y-axis:    revenue
Type:      horizontal bar

# Report → Schedule
Cron:      0 9 * * MON            # Monday 9 AM
Recipients: sales-leadership@company.com
Format:    HTML + CSV attachment
Enter fullscreen mode Exit fullscreen mode

Step-by-step explanation.

  1. The Dataset SQL runs once against the warehouse per report execution. The parameters are resolved from the schedule config (or from URL query params if a user opens the report directly).
  2. Helix caches the result set in memory. The Python cell reads it as a Pandas dataframe via the datasets["..."] interface — no warehouse round-trip.
  3. The Python cell computes the top-10 countries in-notebook. The chart cell renders from that intermediate dataframe.
  4. On schedule, Mode executes the whole flow, renders the report HTML, and emails it to the recipient list with a CSV attachment.
  5. If a viewer opens the report interactively and tweaks the date-range picker, Mode invalidates the Helix cache and re-runs the Dataset — one warehouse round-trip per date change.

Output (top-10 countries, one Monday run).

country revenue
US 125,400
DE 42,300
FR 33,900
UK 28,150
JP 22,700

Rule of thumb. Every reusable SQL logic in Mode belongs in a Dataset with parameters. Reports read Datasets; do not inline SQL in individual report tabs. Datasets are the DRY primitive that keeps a 30-report deployment maintainable.

Worked example — Helix rerun without re-hitting Snowflake

Detailed explanation. An analyst is iterating on chart styling for a report. Each rerun of the Python cell reshapes the data slightly; each chart rerun re-renders. Without Helix, every rerun would re-execute the SQL query against Snowflake, costing warehouse credits. With Helix, only the first run pays warehouse cost; subsequent iterations are free.

Question. Show the timeline of a 10-iteration chart-styling session in Mode, comparing "Helix on" vs a hypothetical "Helix off" scenario.

Input.

Config Warehouse round-trips
Helix on (default) 1 (first run only)
Helix off (hypothetical) 10

Code.

# Session timeline
t=0:  Analyst hits Run
      → SQL Dataset executes against Snowflake (2s + $0.10)
      → Result set (50K rows) lands in Helix
t=15s: Analyst reshapes Python — swap bar for grouped bar
      → Helix read (100ms), no warehouse hit
t=45s: Analyst reshapes Python — add annotation
      → Helix read (100ms), no warehouse hit
...
t=8m:  Analyst tweaks color scheme 8 more times
      → 8 Helix reads (100ms each), no warehouse hits
Enter fullscreen mode Exit fullscreen mode

Step-by-step explanation.

  1. On first Run, the SQL Dataset executes against Snowflake. Warehouse compute costs credits; result set materialises in Helix.
  2. Every subsequent iteration — Python edit, chart color change, dataframe reshape — reads from Helix. Zero warehouse round-trips, sub-second latency.
  3. If the analyst edits the SQL Dataset itself (adds a column, changes a WHERE clause), Helix invalidates and re-executes against Snowflake. The compute cost fires again.
  4. On a scheduled refresh (nightly), Helix invalidates and re-executes as if it were a first run. The report emails ship the fresh data.
  5. Helix's ceiling — ~10M rows per Dataset. Above that, the result exceeds Helix memory and Mode falls back to warehouse-round-trip mode, negating the benefit.

Output.

Metric Helix on Helix off (hypothetical)
Warehouse round-trips 1 10
Snowflake credits $0.10 $1.00
Analyst iteration latency 100 ms 2 s
Total session cost $0.10 $1.00

Rule of thumb. Helix pays for itself the moment an analyst starts iterating. Design SQL Datasets to return the smallest sufficient result set (aggregated, pre-filtered) so Helix stays inside the ~10M-row ceiling.

Worked example — scheduled report with subscribed recipients

Detailed explanation. A weekly executive digest — top-5 KPIs, delivered to the CEO's inbox every Monday morning. The Mode Report Schedule feature is the canonical answer. No Airflow DAG, no cron job, no Slackbot — just a config on the report.

Question. Configure a weekly Monday-9-AM scheduled email of the "Weekly Exec KPIs" report; include HTML + CSV; recipients are the exec-team distribution list.

Input.

Field Value
Cron 0 9 * * MON
Timezone America/Los_Angeles
Recipients exec-team@company.com
Attachments HTML preview + CSV of each Dataset
Owner analytics-lead@company.com

Code.

# Mode Report → Schedule config
Frequency:     Weekly
Day:           Monday
Time:          09:00 (America/Los_Angeles)
Recipients:    exec-team@company.com
Subject line:  "Weekly Exec KPIs — {{ report_date }}"
Body:          "Attached is this week's KPI digest. See report for detail."
Attachments:
  - HTML preview: yes
  - CSV per Dataset: yes
Owner:         analytics-lead@company.com
Retry:         3 attempts, 15-min back-off
Enter fullscreen mode Exit fullscreen mode
# On schedule fire:
# 1. Mode wakes up at Monday 09:00 PT
# 2. Executes every Dataset in the report (Snowflake round-trips)
# 3. Reruns the Python cell + rerenders the chart cells
# 4. Composes an HTML email with the rendered report
# 5. Sends to exec-team@company.com
# 6. On failure, retries 3× with back-off; owner is notified on final failure
Enter fullscreen mode Exit fullscreen mode

Step-by-step explanation.

  1. The Schedule config lives on the report, not on a separate DAG. Every report can carry its own schedule; there is no external orchestration layer.
  2. On fire time, Mode's scheduler executes every Dataset in the report against the warehouse. Helix's cache is invalidated for the run so the data is fresh.
  3. Python and chart cells re-run in top-to-bottom order (Mode is sequential, not reactive). The rendered HTML is composed with the current chart images.
  4. The email is sent via Mode's SMTP; delivery status is logged in the report's Schedule Runs tab. Failures trigger owner notification.
  5. This pattern replaces the classic "Airflow DAG that runs a notebook and emails a PDF" — one Mode config replaces ~200 lines of orchestration code.

Output.

Trigger Snowflake round-trips Recipients Format
Manual open 1 (first) then Helix 1 (viewer) HTML
Scheduled fire 1 (fresh) N (distribution list) HTML + CSV
Failed schedule 0 (aborted after retries) 1 (owner alert) plain text alert

Rule of thumb. For any digest that ships on a cadence to a distribution list, Mode's Schedule feature is the shortest path. Reserve Airflow for actual data-pipeline orchestration; reports do not belong in a DAG.

Senior interview question on Mode dataset governance

A senior interviewer might ask: "Your Mode workspace has 400 reports and 1,200 SQL queries. The finance team complains that DAU shows a different number in three different reports. How do you fix the drift and prevent it in the future — using only Mode's built-in features?"

Solution Using shared Datasets + semantic-layer promotion

Fix plan — Mode metric drift

1. Audit: grep every SQL query for `COUNT(DISTINCT user_id)` — find the
   three flavors of DAU currently used.
2. Pick the canonical definition (usually the strictest: excludes internal
   users, respects timezone).
3. Create a shared Dataset "metric__dau" with the canonical SQL and
   parameters {{ start_date }}, {{ end_date }}, {{ country }}.
4. Rewrite every report to reference `datasets["metric__dau"]` instead of
   inlining COUNT(DISTINCT user_id).
5. Delete the three legacy flavors; add a linter rule (or a ThoughtSpot
   semantic-layer rule) that forbids ad-hoc COUNT(DISTINCT user_id) queries.
6. Promote all core metrics (DAU, MAU, revenue, retention) into the
   semantic layer so ThoughtSpot search analytics uses the same
   definitions as the Mode reports.
Enter fullscreen mode Exit fullscreen mode

Step-by-step trace.

Step Before After
DAU query variants 3 1
Reports affected 12 12 (rewired to metric__dau)
SQL queries in workspace 1,200 1,155 (deleted 45 duplicates)
Metric drift risk high zero (linter enforced)
Semantic-layer surface none dau + mau + revenue + retention

After the refactor, every report that shows DAU reads from metric__dau. Change the metric once in the Dataset (or in the ThoughtSpot semantic layer), every downstream report updates on the next schedule fire.

Output:

Report DAU value before DAU value after
Weekly Growth Digest 42,300 41,780
Product Team Report 43,900 41,780
Finance Dashboard 40,100 41,780
CFO Board Deck (varies weekly) 41,780

Why this works — concept by concept:

  • One Dataset per business concept — the governance win. If DAU has one Dataset, drift is impossible; every report is a reader of that one definition.
  • Parameters keep it reusable{{ start_date }} + {{ country }} let the same Dataset serve every report without duplicating SQL for each date range or filter.
  • Helix caches the shared Dataset — reports that use metric__dau share the same Helix cache within a session, so cost is amortised.
  • Semantic-layer promotion is the belt-and-suspenders — ThoughtSpot's semantic layer sits above Mode Datasets and provides the same canonical definition to search queries, dashboards, and Slack integrations.
  • Cost — the refactor is a one-time investment (O(reports) effort). Ongoing cost is negligible; drift risk drops to zero as long as the linter is enforced in code review.

SQL
Topic — sql
SQL query governance problems

Practice →

SQL Topic — joins SQL join problems

Practice →


4. Sigma — spreadsheet-first cloud analytics

sigma computing is a spreadsheet UX over the warehouse — every formula compiles to SQL and pushes down, no data movement

The mental model in one line: a Sigma workbook is a page-based document where every element (table, pivot, chart, input) is a spreadsheet-style construct backed by a compiled SQL query against the connected cloud warehouse; no data leaves the warehouse, and input tables let non-technical users write data back. Once you say "spreadsheet cells are SQL formulas, workbooks are pages of elements, everything pushes down," every sigma computing interview question becomes a deduction from "the analyst-friendly grid is a warehouse-native query planner in disguise."

Visual diagram of Sigma spreadsheet-first pushdown — a spreadsheet grid card on the left with a highlighted formula cell, a purple compiler funnel in the middle converting formulas into SQL glyphs, and a blue warehouse cylinder on the right receiving the pushed-down query; plus an input-tables side card showing write-back arrows and multiplayer cursors; on a light PipeCode card.

The spreadsheet UX.

  • Workbook — the top-level container, roughly a Google Sheets document.
  • Page — a tab within the workbook, holding elements.
  • Element — a table, pivot, chart, control input, or text block. Every element has a data source (a warehouse table, view, or query).
  • Cell — inside a table element, cells behave like spreadsheet cells with formulas. Formulas can reference other cells, columns, or elements.

Pushdown SQL — the compiler.

  • Every spreadsheet formula is translated to SQL by Sigma's compiler. SUM(amount) becomes SUM(amount) in the generated SQL. IF(status = 'paid', amount, 0) becomes a CASE expression.
  • Pivots compile to GROUP BY. Filters compile to WHERE. Sort-and-limit compile to ORDER BY ... LIMIT. Joins between elements compile to JOIN in the generated query.
  • The compiler pushes as much as possible to the warehouse. What cannot push (e.g. custom Excel-style array formulas that need row-by-row evaluation) is executed in Sigma's post-processing layer.

No data movement — the security story.

  • Because every operation pushes down, the raw data never leaves the warehouse. Sigma sees only the query results, and only the rows the query returned.
  • Warehouse-level access controls (Snowflake role-based, BigQuery IAM, Databricks Unity Catalog) apply directly. Sigma queries execute as the user's warehouse credentials or a service account.
  • For SOC 2 / HIPAA / GDPR customers, this is the differentiator vs Hex / Mode / Deepnote — those tools cache results outside the warehouse; Sigma does not.

Input tables — write-back to the warehouse.

  • A linked input table lets users type into cells; the changes persist to a warehouse table.
  • A CSV upload is the same pattern for a one-shot bulk import.
  • Use cases: forecast overrides, budget entry, categorisation, manual data corrections.
  • Governance: input tables have audit logs (who changed what, when) and role-based edit permissions.

Live-edit multiplayer.

  • Multiple users can edit the same workbook simultaneously — Google-Docs-style with presence indicators.
  • Every change is a versioned event; the workbook has a full revision history.
  • Named branches (paid tier) let two analysts explore alternative versions before merging.

Workbook + element permission model.

  • Workbook-level permission (View / Explore / Edit).
  • Element-level restrictions can hide sensitive columns or filter rows for specific viewer groups.
  • Data source access flows from warehouse credentials — a user without Snowflake access to a table cannot see it in Sigma even if the workbook references it.

Common interview probes on Sigma.

  • "Why is pushdown SQL important?" — because it means the aggregation happens in the warehouse (fast, scalable) instead of in the tool's memory (slow, capped). For 1B-row tables, it is the only workable model.
  • "What is a Sigma input table?" — a spreadsheet region wired to a warehouse table where user edits persist back. Common for budget overrides and manual data entry.
  • "How does Sigma compare to Tableau?" — Sigma is spreadsheet-native (every user knows the paradigm) and warehouse-native (every op is SQL); Tableau is chart-native (drag-drop shelves) and extract-based (data movement out of the warehouse). Different centres of gravity for different teams.
  • "Where does Sigma break?" — spreadsheet-style array formulas that need per-row evaluation force the query result into Sigma's memory, defeating pushdown. Also, custom SQL cells exist but are second-class citizens.

Worked example — pivot compiles to SQL

Detailed explanation. An analyst drags the orders table into a workbook page, adds a pivot with country on rows and SUM(amount) on columns. Sigma's compiler generates a GROUP BY country SQL query and pushes it to Snowflake. The user sees the result grid; the analyst never wrote SQL.

Question. Show the spreadsheet interaction, the generated SQL Sigma compiles, and where the aggregation actually executes.

Input.

orders (Snowflake table)
order_id INT
country STRING
amount DECIMAL
created_at TIMESTAMP

Code.

# Analyst interaction — no SQL typed
1. Drag `orders` table into workbook page.
2. Convert element type: Table → Pivot.
3. Rows shelf: country
4. Values shelf: SUM(amount)
5. Filter shelf: created_at >= LAST_7_DAYS
Enter fullscreen mode Exit fullscreen mode
-- Sigma compiler output — the generated SQL that hits Snowflake
SELECT country,
       SUM(amount) AS sum_amount
FROM   orders
WHERE  created_at >= DATEADD('day', -7, CURRENT_TIMESTAMP)
GROUP  BY country
ORDER  BY country;
Enter fullscreen mode Exit fullscreen mode
# Result — only 5 rows come back to Sigma
country  | sum_amount
US       | 125,400
DE       | 42,300
FR       | 33,900
UK       | 28,150
JP       | 22,700
Enter fullscreen mode Exit fullscreen mode

Step-by-step explanation.

  1. Dragging orders into the page creates an element that references the Snowflake table by fully-qualified name. Sigma queries the warehouse for the schema; no rows are fetched yet.
  2. Converting the element to Pivot puts Sigma in aggregation mode. The Rows and Values shelves declare GROUP BY country and SUM(amount) respectively.
  3. Sigma compiles the pivot config + filter shelf into the SQL above. The compiled SQL is visible in the element's Query pane (analyst-worthy for debugging).
  4. Snowflake executes the query; the 5-row result set returns to Sigma. Data transferred: ~1 KB.
  5. Every subsequent filter change (adding a country filter, changing the date range) regenerates the SQL and re-executes. Sigma does not cache results — every interaction is a fresh warehouse query. That is the price of the no-data-movement guarantee.

Output.

country sum_amount
US 125,400
DE 42,300
FR 33,900
UK 28,150
JP 22,700

Rule of thumb. For any Sigma workbook that will see heavy stakeholder use, materialise upstream aggregates in the warehouse (dbt models, scheduled tasks) so the pushdown queries are cheap. Sigma's no-cache model amplifies the cost of unoptimised warehouse queries.

Worked example — input table for a forecast override

Detailed explanation. The finance team wants to enter monthly forecast overrides in a spreadsheet-like UI, and have those overrides persist to a warehouse table for downstream models. Sigma's linked input tables are the exact pattern.

Question. Set up an input table forecast_overrides in a Sigma workbook so finance can type into cells and the changes write back to a Snowflake table.

Input.

forecast_overrides (Snowflake table)
month DATE (PK)
country STRING (PK)
override_amount DECIMAL
entered_by STRING
entered_at TIMESTAMP

Code.

# Sigma workbook setup — no SQL typed
1. Add new element → Input Table → Linked
2. Target table: my_warehouse.finance.forecast_overrides
3. Editable columns: [override_amount]
4. Auto-filled columns: [entered_by, entered_at]
5. Permission: Edit access for finance-team@ role only
6. Audit log: enabled
Enter fullscreen mode Exit fullscreen mode
-- Under the hood — Sigma runs these against Snowflake
-- On row insert:
INSERT INTO finance.forecast_overrides
   (month, country, override_amount, entered_by, entered_at)
VALUES
   ('2026-08-01', 'US', 500000, 'alice@company.com', CURRENT_TIMESTAMP());

-- On row edit:
UPDATE finance.forecast_overrides
SET    override_amount = 550000,
       entered_by      = 'bob@company.com',
       entered_at      = CURRENT_TIMESTAMP()
WHERE  month = '2026-08-01' AND country = 'US';
Enter fullscreen mode Exit fullscreen mode
# Finance team interaction — spreadsheet UX
month       | country | override_amount    | entered_by       | entered_at
2026-08-01  | US      | [type here → 500K] | alice@company    | (auto)
2026-08-01  | DE      | [type here → 120K] | alice@company    | (auto)
2026-09-01  | US      | [type here → 620K] | alice@company    | (auto)
Enter fullscreen mode Exit fullscreen mode

Step-by-step explanation.

  1. Sigma sets up the input table by pointing to the Snowflake target table. It reads the schema and pre-fills the columns as spreadsheet columns.
  2. Editable columns (override_amount) accept keyboard input from the user. Auto-filled columns (entered_by, entered_at) are populated by Sigma at write time.
  3. Every keystroke that finalises a cell (Enter, Tab) triggers a Sigma request to write to Snowflake. The write is idempotent by primary key.
  4. Audit log records who edited which row and when. Compliance-friendly for SOX-style controls.
  5. Downstream dbt models can JOIN forecast_overrides ON month, country to apply the overrides on top of the base forecast. The finance team owns the override table; DE owns the model.

Output (Snowflake table after 3 finance entries).

month country override_amount entered_by entered_at
2026-08-01 US 500,000 alice@company 2026-07-17 09:30:12
2026-08-01 DE 120,000 alice@company 2026-07-17 09:31:04
2026-09-01 US 620,000 alice@company 2026-07-17 09:32:47

Rule of thumb. Input tables replace the "finance sends a CSV over email; DE loads it every Monday" pattern. Wire the input table once, hand finance the workbook link, ship the manual pipeline.

Worked example — element-level RLS via warehouse role

Detailed explanation. A workbook shows global revenue by country. The regional-manager users should see only their region's data. Sigma leverages the warehouse's role-based access control: the query runs as the viewing user's Snowflake role, and Snowflake's row-access policies filter the results.

Question. Configure Sigma so each regional manager sees only their region's rows in the pivot, using Snowflake row-access policies as the enforcement layer.

Input.

User Snowflake role Region visible
alice@ eu_manager EU
bob@ us_manager US
carol@ apac_manager APAC

Code.

-- Snowflake — the row-access policy (defined once, applied to orders)
CREATE ROW ACCESS POLICY orders_by_region AS (region STRING)
RETURNS BOOLEAN ->
    CASE CURRENT_ROLE()
        WHEN 'EU_MANAGER'   THEN region = 'EU'
        WHEN 'US_MANAGER'   THEN region = 'US'
        WHEN 'APAC_MANAGER' THEN region = 'APAC'
        ELSE                     FALSE
    END;

ALTER TABLE orders
    ADD ROW ACCESS POLICY orders_by_region ON (region);
Enter fullscreen mode Exit fullscreen mode
# Sigma workbook — no additional config; queries run as the user's role
1. Ensure Sigma workspace uses OAuth-per-user (not a shared service account).
2. User alice@ opens the workbook.
3. Sigma authenticates against Snowflake as alice@'s eu_manager role.
4. The pivot query hits Snowflake; the row-access policy filters to region='EU'.
5. Alice sees only EU rows in the pivot.
Enter fullscreen mode Exit fullscreen mode

Step-by-step explanation.

  1. Sigma delegates authentication to Snowflake. When alice@ opens the workbook, her Snowflake OAuth token is used for the query.
  2. Snowflake evaluates the row-access policy at query time. The policy inspects CURRENT_ROLE() and returns TRUE only for rows matching alice's region.
  3. The compiled SQL is identical for every user — Sigma does not rewrite the query per user. The row filtering happens inside Snowflake, invisible to Sigma.
  4. Bob and Carol see different rows for the exact same pivot, because their Snowflake role is different.
  5. This pattern eliminates a whole class of RLS bugs — Sigma cannot leak data even in a misconfiguration, because the warehouse enforces the filter.

Output (three users see three different results from the same pivot).

alice sees (EU rows) bob sees (US rows) carol sees (APAC rows)
DE, FR, UK US JP, IN, AU
5 rows 3 rows 4 rows

Rule of thumb. For any SaaS analytics tool, push RLS to the warehouse via row-access policies. Do not implement RLS at the tool level (workbook filters, view whitelists) — those are bypassable; warehouse policies are not.

Senior interview question on Sigma pushdown cost

A senior interviewer might ask: "Your Sigma workbook has 12 elements sourced from orders, a 1B-row table. Every filter change on the page fires 12 warehouse queries. Snowflake credits are spiralling. How do you reduce the cost without changing the workbook's user experience?"

Solution Using materialised upstream aggregates + workbook-level filters

-- Fix 1 — materialise the shared aggregate in dbt
-- models/marts/orders_by_country_daily.sql
{{ config(materialized='table') }}

SELECT DATE_TRUNC('day', created_at) AS day,
       country,
       COUNT(*)                       AS order_count,
       SUM(amount)                    AS revenue,
       COUNT(DISTINCT user_id)        AS uniques
FROM   {{ ref('orders') }}
WHERE  created_at >= DATEADD('day', -365, CURRENT_DATE)
GROUP  BY 1, 2;

-- This 3M-row table replaces the 1B-row orders for every workbook element.
Enter fullscreen mode Exit fullscreen mode
# Fix 2 — Sigma workbook wiring
1. Rewire all 12 elements to `orders_by_country_daily` instead of `orders`.
2. Move filter (date range, country) from element-level to workbook-level.
3. The compiled query per element is now a scan of a 3M-row table
   with pre-aggregated columns — sub-second, cheap.
Enter fullscreen mode Exit fullscreen mode
# Fix 3 — Snowflake result cache
1. Ensure Snowflake result cache is on (default: yes, 24 hrs).
2. Workbook-level filters produce the same SQL across users on the same
   day → cache hit rate jumps from ~10% to ~90%.
Enter fullscreen mode Exit fullscreen mode

Step-by-step trace.

Step Before After
Element data source orders (1B rows) orders_by_country_daily (3M rows)
Warehouse queries per filter change 12 12 (but hitting 3M rows)
Rows scanned per query ~30M (7-day filter × 1B) ~15K (7-day filter × 3M)
Snowflake result cache hit ~10% ~90%
Total scan cost per filter change ~360M rows ~15K rows
Cost per session $1.20 $0.02

After the refactor, the workbook UX is unchanged (same 12 elements, same filters). Cost drops ~60x because every query hits a pre-aggregated model with a warm result cache.

Output:

Metric Before After
Snowflake credits/day (workbook) 240 4
Avg query latency 4.2 s 0.4 s
Cache hit rate 10% 90%
Analyst iteration friction high low

Why this works — concept by concept:

  • Materialise the shared aggregate — 12 elements sharing the same aggregation grain (day × country) is a signal that the aggregation belongs in a dbt model, not in every element. Compute once, serve 12 times.
  • Workbook-level filters — moving filters from element scope to workbook scope means all 12 elements share the same WHERE clause. Result cache hit rate rises because the queries are identical across users.
  • Pre-aggregated model is cheap to scan — 3M rows fits in Snowflake's smallest warehouse tier; scans complete in sub-second. The rows-scanned metric drops from ~30M to ~15K.
  • Sigma's no-cache model amplifies warehouse cost — because Sigma issues fresh queries for every interaction, the warehouse-side cache and pre-aggregation are the only cost controls. This is exactly the opposite of Mode/Helix.
  • Cost — the dbt model refresh runs nightly, costing ~$0.50/day; the workbook cost drops from ~$1.20/session to ~$0.02/session. Break-even at 3 sessions/day.

SQL
Topic — aggregation
Pushdown aggregation problems (SQL)

Practice →

SQL Topic — window-functions Window function drills (SQL)

Practice →


5. Deepnote — Jupyter-compatible collaborative notebook

deepnote is Jupyter with multiplayer editing — same kernel, same .ipynb, real-time collaboration on top

The mental model in one line: a Deepnote notebook is a block-based document that runs a Jupyter-compatible Python kernel, supports real-time multiplayer editing (Google-Docs-style), syncs bidirectionally with GitHub, and can be published as a Streamlit-style app for stakeholders. Once you say "Jupyter under the hood, collaboration on top, apps at the edge," every deepnote vs hex interview question becomes a deduction from "Deepnote is where the data-science team lives; the others target the analytics team."

Visual diagram of Deepnote and the 5-question notebook decision tree — a Deepnote notebook card on the left with three block-cells, two multiplayer cursors, and a GitHub sync arrow; on the right, a vertical decision tree of five diamond nodes ending in four medallions for Hex, Mode, Sigma, and Deepnote; on a light PipeCode card.

The three cell types.

  • Code cell — runs Python (or R, or Julia) in a per-project kernel. %%sql magic converts a code cell into a SQL cell bound to a data source connection.
  • Markdown cell — rich text with headings, callouts, tables, and inline code.
  • Chart cell — a low-code chart builder that wraps Plotly / Vega under the hood; also supports Python-code-defined charts.

Jupyter kernel compatibility.

  • The kernel is a real Jupyter kernel — every .ipynb file works. Import an existing Jupyter notebook, edit it in Deepnote, export back to .ipynb.
  • IPython magics (%time, %%capture, %%sql) work as expected. Third-party kernels (R via IRkernel, Julia via IJulia) are supported.
  • The trade-off: no reactive execution. Every cell runs sequentially, top-to-bottom, on click.

Real-time multiplayer editing.

  • Multiple users can edit the same notebook simultaneously; each user's cursor is visible with a colored flag and name.
  • Cell locking prevents two users from typing into the same cell at once.
  • Comments on cells (Google-Docs-style) let one user leave a review note without editing the code.
  • Revision history is per-cell, timestamped, restorable.

GitHub sync — bidirectional.

  • Connect a Deepnote project to a GitHub repo. Every notebook is a .ipynb file in the repo.
  • Editing in Deepnote pushes to GitHub on commit. Editing on GitHub (via CI, review, or web edit) pulls back to Deepnote.
  • The bidirectional sync is what makes Deepnote fit the DE workflow — code review happens in the same PR flow as the rest of the code.

Environment management.

  • Simple: a requirements.txt at the project root. Deepnote installs the listed packages in the kernel.
  • Reproducible: a Dockerfile at the project root. Deepnote builds and runs the image as the kernel environment. This is the pattern for teams that need pinned CUDA versions, native dependencies, or non-Python libraries.
  • Shared: a workspace-level base image for consistency across projects.

AI Copilot.

  • Suggests code (Python, SQL) as you type.
  • Prompt-driven cell generation: "add a bar chart of top 10 countries by revenue."
  • Explain-this-code button on any cell.
  • The AI reads the current notebook context (upstream cell outputs, connected data sources) for better suggestions.

Streamlit-style apps.

  • Any Deepnote notebook can be published as an app.
  • Block-based cells become app widgets (dropdowns, sliders, buttons).
  • Apps run on a separate kernel; edits to the notebook do not disturb the app session.
  • The pattern is Streamlit-like but without needing to switch tools — the same notebook is both the authoring surface and the delivery surface.

Pricing model (2026).

  • Free — 3 users per workspace, small compute, GitHub sync included.
  • Pro — per-seat, ~$20 per editor, larger compute quota.
  • Team / Enterprise — SSO, VPC, custom compute tiers, audit logging, private base images.

Common interview probes on Deepnote.

  • "How does Deepnote differ from Jupyter?" — Jupyter kernel + real-time multiplayer + GitHub sync + AI copilot + block-based cells + apps. The kernel is the same; the collaboration is the addition.
  • "When would you pick Deepnote over Hex?" — when the team is DS-heavy, lives in Python, needs GitHub sync for code review, and does not need reactive execution.
  • "How does GitHub sync handle merge conflicts?" — cell-level diffs on .ipynb files. Deepnote highlights conflicting cells; the user resolves in the editor before pulling.
  • "What is a Deepnote app?" — a notebook published with input widgets exposed and code cells hidden; runs on a separate kernel from the editor.

Worked example — Jupyter notebook imported into Deepnote

Detailed explanation. A data scientist has a 500-line Jupyter notebook analyzing customer churn. Uploading the .ipynb to Deepnote should preserve every cell, every output, and every dependency. The kernel compatibility promise is what enables this.

Question. Import a Jupyter notebook churn_analysis.ipynb into Deepnote; add multiplayer editing; sync to GitHub; publish as an app for the PM.

Input.

File Size Cells Dependencies
churn_analysis.ipynb 500 lines 42 cells (36 code, 6 markdown) pandas, scikit-learn, plotly

Code.

# Deepnote — import workflow
1. New project → Import → upload churn_analysis.ipynb
2. Deepnote detects requirements.txt (or infers from imports)
3. Kernel starts; every cell's cached output is preserved
4. Add teammate → they join the project with edit permission
5. Both cursors visible in the notebook, no re-run required to sync state
Enter fullscreen mode Exit fullscreen mode
# requirements.txt (auto-inferred or hand-written)
pandas==2.2.0
scikit-learn==1.4.1
plotly==5.19.0
duckdb==0.10.0
Enter fullscreen mode Exit fullscreen mode
# GitHub sync
1. Project → Settings → Connect to GitHub → pick repo + branch
2. Notebook auto-committed to `notebooks/churn_analysis.ipynb`
3. Every save creates a commit with message "Deepnote: edit by alice@"
4. External PR review flow: change requested in GitHub → pull back into Deepnote
Enter fullscreen mode Exit fullscreen mode
# Publish as app
1. Publish → App view
2. Input widgets: `cutoff_date` (date), `region` (dropdown)
3. Hidden cells: code cells; visible: charts + text
4. Share link with PM → PM never sees the code, only the interactive result
Enter fullscreen mode Exit fullscreen mode

Step-by-step explanation.

  1. The .ipynb import is lossless because Deepnote's storage format is .ipynb itself. Every cell's metadata, source, and cached output survives.
  2. The kernel boots with the inferred (or hand-written) requirements.txt installed. First-boot takes ~30s; subsequent boots hit a cached image.
  3. Multiplayer editing works cell-by-cell — one user cannot type into a cell another user is editing (cell lock). Cursor presence indicators show where each user is.
  4. GitHub sync converts every save into a commit. External PRs can update the notebook; Deepnote merges the change on the next pull.
  5. The app view surfaces input widgets and hides code cells. The PM sees a stakeholder-friendly interface; the DS team keeps editing the notebook.

Output (post-import project state).

Item Value
Cells preserved 42/42
Cached outputs 36/36
Kernel boot time 30 s
GitHub repo github.com/company/analytics/notebooks/churn_analysis.ipynb
App URL deepnote.com/project/churn-analysis/app

Rule of thumb. For any team already living in Jupyter and using GitHub for code review, Deepnote is the shortest path to multiplayer + apps. Do not migrate to Hex if the team's mental model is Jupyter — pay for Deepnote and get the collaboration layer.

Worked example — %%sql cell with connection to Snowflake

Detailed explanation. A DS cell needs to pull data from Snowflake without writing a snowflake-connector-python boilerplate. Deepnote's %%sql magic + connection integration is the shortest path.

Question. Set up a Snowflake connection in Deepnote, use %%sql to query, bind the result to a Pandas dataframe, and plot with Plotly.

Input.

Connection Value
Type Snowflake
Account company.snowflakecomputing.com
Warehouse analytics_wh
Auth OAuth-per-user

Code.

# Deepnote — Integrations → Add → Snowflake
1. Enter account URL, warehouse name, database, schema
2. Authenticate via OAuth (or service account)
3. Name the connection: `snowflake_analytics`
4. Test connection → success
Enter fullscreen mode Exit fullscreen mode
# Cell 1 — SQL magic bound to `snowflake_analytics`
%%sql --connection snowflake_analytics --output orders_df
SELECT country,
       DATE_TRUNC('day', created_at) AS day,
       SUM(amount)                    AS revenue
FROM   orders
WHERE  created_at >= CURRENT_DATE - 30
GROUP  BY 1, 2
ORDER  BY day, revenue DESC;
Enter fullscreen mode Exit fullscreen mode
# Cell 2 — Python cell reads the dataframe
import plotly.express as px
fig = px.line(
    orders_df,
    x="day",
    y="revenue",
    color="country",
    title="Revenue by country — last 30 days",
)
fig.show()
Enter fullscreen mode Exit fullscreen mode

Step-by-step explanation.

  1. The Integrations panel handles credentials; the notebook code never sees the password or the service-account key. Best practice for shared workspaces.
  2. %%sql --connection ... --output orders_df runs the query against Snowflake and binds the result to a Pandas dataframe named orders_df. The variable is now available to every downstream cell.
  3. The Python cell reads orders_df and plots with Plotly. Rerun cells manually — Deepnote is sequential, not reactive. Change the SQL cell → click Run on the Python cell.
  4. If the query is expensive, Deepnote caches the last result in the kernel; a rerun of the same cell without changes returns instantly.
  5. For collaborative sessions, both users share the same kernel and see the same orders_df in the shared kernel state — no local re-execution needed.

Output (orders_df — first 5 rows).

country day revenue
US 2026-07-16 42,300
DE 2026-07-16 12,800
FR 2026-07-16 8,900
US 2026-07-15 40,150
DE 2026-07-15 11,700

Rule of thumb. For any team-shared notebook, wire data source credentials via Integrations, not via inline environment variables. Keeps secrets out of the .ipynb file (which lives in GitHub) and makes multiplayer sessions credential-safe.

Worked example — publish notebook as Streamlit-style app

Detailed explanation. The DS team's churn analysis needs to reach the PM without a hand-off. Deepnote's app view exposes input widgets and hides code cells. Same notebook, two audiences.

Question. Publish the churn_analysis.ipynb notebook as an app with two input widgets (cutoff_date, region); hide the code cells; share the link.

Input.

Widget Variable Bound to
date input cutoff_date SQL cell WHERE clause
dropdown region SQL cell WHERE clause

Code.

# Cell — input widget declaration (Deepnote block-based cell)
cutoff_date = deepnote.input.date(
    label="Cutoff date",
    default="2026-07-01",
)
region = deepnote.input.select(
    label="Region",
    options=["North America", "EU", "APAC"],
    default="North America",
)
Enter fullscreen mode Exit fullscreen mode
# Cell — SQL cell using the inputs (Jinja substitution)
%%sql --connection snowflake_analytics --output churn_df
SELECT user_id,
       churn_score
FROM   fct_churn_scores
WHERE  as_of_date = {{ cutoff_date }}
  AND  region     = {{ region }};
Enter fullscreen mode Exit fullscreen mode
# Cell — plot the churn score histogram
import plotly.express as px
fig = px.histogram(churn_df, x="churn_score", nbins=30,
                    title=f"Churn score distribution — {region} @ {cutoff_date}")
fig.show()
Enter fullscreen mode Exit fullscreen mode
# Publish → App view
Visible cells:  [input widgets, plot]
Hidden cells:   [SQL cell]
Kernel:         separate from editor
URL:            deepnote.com/project/churn/app
Permission:     PM has view-only access via SSO
Enter fullscreen mode Exit fullscreen mode

Step-by-step explanation.

  1. Input widgets are declared with deepnote.input.* helpers. The values become Python variables that the SQL cell can reference via Jinja substitution.
  2. The SQL cell runs against Snowflake each time the widget values change (sequential re-run — the PM clicks a Run button, or the widget's auto-run option fires it).
  3. The Python cell reads churn_df and plots. The plot is what the PM sees; the code is hidden in app view.
  4. App runs on a separate kernel from the editor. Multiple concurrent app viewers share app kernels; the DS team's notebook editing does not disturb them.
  5. Compared to Hex: Deepnote apps are sequential (rerun on click); Hex apps are reactive (auto-rerun on widget change). Deepnote is closer to Streamlit; Hex is closer to Observable.

Output (app view — what the PM sees).

Element Purpose
Cutoff date input pick as-of date
Region dropdown pick region
Histogram churn score distribution
Rerun button manual trigger (SQL + plot)

Rule of thumb. For a stakeholder who needs a self-service view and the DS team already has the analysis in a notebook, publish as a Deepnote app. It is the shortest distance between "notebook" and "stakeholder-usable artifact" for a Python-first team.

Senior interview question on notebook decision in 2026

A senior interviewer might frame this as: "You are the analytics engineering lead at a 50-person company. You have to pick one SQL notebook to standardise on — for both the analyst team and the DS team. Walk me through the 5-question decision tree and show your recommendation."

Solution Using the 5-question notebook framework

5-question SQL notebook decision tree — Hex / Mode / Sigma / Deepnote
=====================================================================

Q1 — Is your primary user SQL-first or Python-first?
   - SQL-first    → Hex / Mode / Sigma
   - Python-first → Deepnote

Q2 — Do you need real-time reactive execution?
   - Yes → Hex (unique in the category)
   - No  → Mode / Sigma / Deepnote

Q3 — Is the delivery surface an exec dashboard or an analyst exploration?
   - Exec dashboard    → Sigma (spreadsheet UX) or Hex publish-to-app
   - Analyst exploration → Hex / Mode / Deepnote

Q4 — Do spreadsheet users (non-technical) need to build too?
   - Yes → Sigma (only spreadsheet-native option)
   - No  → Hex / Mode / Deepnote

Q5 — What's the budget: prosumer or enterprise?
   - Prosumer  → Mode / Deepnote (starts free / cheap)
   - Enterprise → Hex / Sigma (both trend >$50/seat/mo with SSO + VPC)

Engine-specific trade-offs
--------------------------
Hex
  + Reactive cell graph — safest for stakeholder-facing apps
  + dbt semantic layer integration (first-class)
  + Hex Magic AI reads the cell graph for smarter completions
  − Enterprise pricing; steep learning curve for pure Jupyter users
  − Sequential fallback still requires notebook discipline

Mode
  + SQL-first UX; Datasets library keeps queries DRY
  + Helix in-memory engine slashes iteration cost
  + Scheduled email reports built-in (no Airflow needed)
  − Sequential Python notebook; no reactive execution
  − Post-ThoughtSpot roadmap is bifurcated

Sigma
  + Spreadsheet UX — non-technical users adopt instantly
  + Pushdown SQL for every op — no data movement, warehouse-native security
  + Input tables enable write-back workflows (forecast overrides)
  − No true notebook — Python is a second-class citizen
  − No result caching — expensive for large tables without upstream aggregation

Deepnote
  + Jupyter-compatible; imports .ipynb losslessly
  + Real-time multiplayer + bidirectional GitHub sync
  + Cheap prosumer tier; scales to enterprise SSO
  − Sequential execution; no reactive graph
  − Not SQL-first — SQL is a magic, not a first-class cell type
Enter fullscreen mode Exit fullscreen mode

Step-by-step trace.

Team requirement Q1 Q2 Q3 Q4 Q5 Pick
Analyst team (SQL + dbt + apps) SQL reactive exploration + app no enterprise Hex
Analyst team (SQL + reports + email digest) SQL no analyst no prosumer Mode
Exec + analyst team (spreadsheet UX, pushdown-heavy) SQL no dashboard yes enterprise Sigma
DS team (Python-first, GitHub, .ipynb-native) Python no exploration no prosumer Deepnote

For the 50-person mixed company: pick Hex for the analytics team (it fits Q1-SQL + Q2-reactive + Q3-app + Q5-enterprise) and Deepnote for the DS team (it fits Q1-Python + Q5-prosumer). Both share dbt as the metric-layer source of truth. Two tools, one metric layer, zero drift.

Output:

Verdict Reasoning
Standardise analytics team on Hex reactive graph + dbt semantic layer + publish-to-app fits SQL-first analyst workflow
Standardise DS team on Deepnote Jupyter compatibility + GitHub sync + Python-first fits DS workflow
Reject: single-tool-fits-all analyst UX and DS UX diverge enough that forcing one tool costs more than two
Reject: Mode ThoughtSpot roadmap uncertainty + no reactive execution
Reject: Sigma alone no code-first path for DS team's Python + ML workflow

Why this works — concept by concept:

  • 5 questions in order — Q1 (primary user) prunes half the options. Q2 (reactive) picks Hex if answered yes. Q3–Q5 fine-tune. Answering out of order wastes cycles debating irrelevant trade-offs.
  • Engine-specific trade-offs — each tool has 2-3 unique strengths and 2-3 hard constraints. Memorising them lets you fall back to "what would push you to X" answers when an interviewer probes deeper.
  • Two tools is often the right answer — the analyst team and the DS team have different mental models; forcing one tool costs UX friction and adoption. Share the metric layer (dbt), not the notebook tool.
  • Metric-layer discipline — the tool choice matters less than the discipline of having dbt own every metric. Pick tools that respect the semantic layer (Hex has first-class support; the others via warehouse views).
  • Cost — notebook standardisation is a 2-year commitment. Migration between notebooks is 3–6 months of analyst friction. Get Q1–Q5 right the first time; the framework prevents the "we bought Hex but the team never adopted it" failure mode.

SQL
Topic — sql
SQL notebook design problems

Practice →

SQL
Topic — joins
SQL join design problems

Practice →


Cheat sheet — SQL notebook recipes

  • When Hex wins. Reactive cell graph is a must (stakeholder-facing apps), dbt semantic layer is the metric source, SQL + Python mixed team, publish-to-app is the delivery surface, enterprise budget available. The 80% case for SQL-first analytics teams in 2026.
  • When Mode wins. SQL editor + scheduled email report is the primary flow, Datasets library keeps queries DRY, Helix in-memory engine matters for cost, ThoughtSpot search analytics is on the roadmap, prosumer pricing beats Hex on budget-sensitive teams.
  • When Sigma wins. Non-technical stakeholder needs to build too (spreadsheet UX), pushdown SQL is a security requirement (no data movement), input tables enable write-back workflows (forecast overrides, categorisation), exec dashboards on top of Snowflake are the primary artifact.
  • When Deepnote wins. Python-first DS team, Jupyter kernel compatibility is required (.ipynb legacy), real-time multiplayer + GitHub sync fits the code-review workflow, Streamlit-style apps replace a separate app tool. Prosumer pricing wins on tight-budget DS teams.
  • Reactive vs sequential rule. Reactive (Hex) for stakeholder-facing apps where a stale downstream cell is a bug. Sequential (Mode / Deepnote / Sigma) for analyst iteration where "click Run" is a feature (control over side effects).
  • Pushdown SQL rule. For tables above ~10M rows, the aggregation must happen in the warehouse. Sigma enforces this by design. Hex + Mode + Deepnote enable it via SQL cells but do not enforce it — analysts can still SELECT * and OOM the kernel.
  • Semantic-layer integration rule. Hex has first-class dbt semantic-layer support. Mode has ThoughtSpot's semantic layer via post-acquisition integration. Sigma and Deepnote rely on warehouse views for metric consistency. If you have dbt Cloud, Hex is the low-friction pick.
  • Notebook DAG dedup rule. In reactive notebooks (Hex), cells sharing the same upstream compute should share a single cached cell — not duplicate the query 3x. In sequential notebooks (Mode / Deepnote), the %%capture or intermediate-dataframe pattern serves the same role.
  • Publish-to-app recipe (Hex). Add input widgets → parameterise SQL via Jinja → click Publish → share the app URL. Input widgets encode as URL query params; the app view runs on a separate kernel from the editor.
  • Scheduled email recipe (Mode). Create the report → Schedule tab → cron expression + recipient list + HTML/CSV format. Retries and owner alerts are built-in; no external orchestration required.
  • Pushdown cost control (Sigma). Materialise shared aggregates in dbt; wire workbook elements to the aggregate table (not the raw source); use workbook-level filters (not element-level) to maximise Snowflake result cache hit rate.
  • GitHub sync recipe (Deepnote). Connect project → pick repo + branch → every save auto-commits. Merge conflicts surface as cell-level diffs; resolve in the editor before pulling. Fits the DE code-review workflow.
  • Metric drift firewall. Every notebook / dashboard / report reads metrics via the semantic layer (dbt) — never redefine COUNT(DISTINCT user_id) inline. Enforce via linter in code review; audit quarterly.
  • Row-level security recipe (Sigma). Push RLS to the warehouse via Snowflake row-access policies (or BigQuery row-level security). Sigma inherits automatically because every query runs as the user's warehouse role. Do not implement RLS at the workbook level.

Frequently asked questions

Is Hex the same as Mode?

No — they occupy overlapping but distinct product categories. Both are SQL-first collaborative notebooks with Python cells and chart cells, but the execution model and delivery surface diverge sharply. Hex is a reactive notebook — a cell dependency graph auto-reruns downstream cells when an upstream cell changes, and any notebook can be published as an interactive app. Mode is sequential — a SQL editor tab plus a Python notebook tab plus a rendered report; the delivery surface is a scheduled email or a permalink. Choose Hex when reactive execution and publish-to-app matter; choose Mode when the Datasets library, Helix in-memory engine, and email digest workflow fit. The hex vs mode decision usually comes down to "do we care about reactive?" and "do we ship email reports?" — flip those and you get your answer.

What is a reactive notebook?

A reactive notebook is one where cells declare their inputs (implicitly via variable references or explicitly via widget bindings) and the runtime re-executes downstream cells automatically whenever an upstream cell's output changes. Hex is the canonical sql notebook in this category — change a SQL cell's query, every downstream Python cell, chart, and app viewer updates without a rerun click. The mental model is a dataflow DAG (like Observable, Streamlit, or a spreadsheet), not a top-to-bottom script (like Jupyter). Reactive notebooks are safer for stakeholder-facing apps because there is no such thing as a "forgot-to-rerun-that-cell" bug, but they impose a discipline: side-effecting cells must be wrapped explicitly (in an input-button guard) so an accidental re-run does not double-write.

Sigma vs Tableau — when do I pick Sigma?

Pick Sigma when the primary users are analysts + execs who think in spreadsheets, the warehouse is Snowflake / BigQuery / Databricks, and pushdown SQL (no data movement, warehouse-native security) is a requirement. Pick Tableau when the primary users are visualization-first designers, the workflow revolves around drag-drop chart shelves and rich chart libraries, and extract-based caching is acceptable. Sigma's differentiator is that every operation compiles to SQL and pushes down — the analyst never leaves the warehouse's security boundary, and 1B-row tables are as fast as their warehouse permits. Tableau's differentiator is the chart-authoring UX and the huge existing user base. In 2026, Sigma tends to win at Snowflake-native shops with SOC 2 requirements; Tableau wins at enterprises with a decades-long chart library and BI team already trained on it. The sigma computing interview question is usually "why not Tableau?" — the answer is pushdown + spreadsheet UX.

Can Deepnote replace Jupyter?

Yes — for teams already using Jupyter, Deepnote is a drop-in replacement that adds real-time multiplayer editing, GitHub sync, an AI Copilot, and publish-as-app on top of the same Jupyter kernel. Existing .ipynb files import losslessly (cells, outputs, dependencies, metadata all preserved). The trade-offs: no reactive execution (Deepnote is sequential like Jupyter), and SQL is a %%sql magic rather than a first-class cell type as in Hex. For a data science team that lives in Python, needs GitHub-based code review, and wants multiplayer collaboration without leaving the notebook paradigm, deepnote vs hex almost always ends in Deepnote's favour. The analyst notebook question is different — analysts benefit more from SQL-first tools like Hex or Mode; DS teams benefit more from Deepnote's Jupyter fidelity.

Which SQL notebook is best for a data engineering team?

There is no single best — the answer depends on the team's mix of SQL-first vs Python-first workflows, the delivery surface (interactive app vs scheduled report vs exec dashboard), and the budget. A common 2026 pattern for a mixed DE-plus-analytics team of 20-50: Hex for the analyst team (reactive graph, dbt semantic-layer integration, publish-to-app for stakeholders) plus Deepnote for the DS team (Jupyter compatibility, GitHub sync, Streamlit-style apps). Both share dbt as the metric-layer source of truth — every notebook queries metric('dau', grain='day') instead of redefining the metric inline. If the team is spreadsheet-heavy (execs building too), swap Hex for Sigma; if the team is report-and-email-heavy, keep Mode on the roadmap for the digest workflow. The sql notebook comparison interview surface reduces to "who authors, who consumes, and where does the metric live?"

Hex vs Mode vs Sigma pricing?

All four vendors bill per-seat with editor / creator tiers and separate viewer tiers. Rough 2026 ranges: Hex starts free (Community) and jumps to ~$50/editor/month on Team, custom Enterprise pricing with SSO / VPC / dbt semantic layer. Mode starts free (Studio, legacy) and jumps to ~$50/creator/month on Business, custom Enterprise with SSO. Sigma is generally enterprise-priced, ~$500/user/month at typical negotiated rates for medium-size teams (varies by seat count). Deepnote starts free (3 seats), Pro at ~$20/editor/month, Team + Enterprise custom. The collaborative sql notebook cost trend: prosumer tools (Mode Studio, Deepnote Free) work for solo analysts; the moment you cross 5+ editors with SSO requirements, expect $500-$2000/month per tool at typical enterprise rates. Sigma tends to be the highest-priced enterprise-only option; Deepnote the cheapest per-seat; Hex and Mode in the middle.

Practice on PipeCode

Pipecode.ai is Leetcode for Data Engineering — every Hex, Mode, Sigma, and Deepnote recipe above ships with hands-on practice rooms where you wire the reactive cell graph, the pushdown SQL pivot, and the parameterised Dataset against real graded inputs. PipeCode pairs every reading with 450+ DE-focused problems and a real-time scoring engine, so you never have to wonder whether your `hex vs mode` answer holds up under a senior interviewer's depth probes.

Practice SQL now →
Aggregation drills →

Top comments (1)

Collapse
 
topstar_ai profile image
Luis Cruz

I found the distinction between Hex's reactive dependency-graph notebook and Mode's SQL editor plus scheduled-report engine to be particularly insightful, as it highlights the importance of understanding the underlying mental models of each tool. The fact that Sigma compiles every formula to warehouse SQL is also a game-changer for teams looking to push complex calculations to their data warehouses. Have you considered exploring how these tools integrate with emerging trends like data mesh architectures, and how that might impact the choice of SQL notebook for a given team?