In Apache Airflow, what is the difference between an operator and a sensor?
answer
- one does work, the other waits
- declaring it in the DAG is not running it
- the waiting kind is a subclass, not a sibling
- BaseOperator.execute versus BaseSensorOperator.poke
basics
~20 sIn Airflow an operator is a template for one unit of work — run a Bash command, call a Python function, submit a query. A sensor is an operator that only waits, polling until an external condition becomes true.
solid answer
~50 sAn **operator** is a class that describes one unit of work. Instantiating it inside a DAG creates a **task**, and each scheduled run of that task produces a **task instance**. `BashOperator` runs a shell command, `PythonOperator` calls a callable, and provider packages add things like `BigQueryInsertJobOperator` or `KubernetesPodOperator`. They all subclass `BaseOperator` and do their work inside `execute(context)`. A **sensor** is a subclass of `BaseSensorOperator` — still an operator, but its job is to block until something is there: `S3KeySensor` waits for an object, `ExternalTaskSensor` waits for a task in another DAG, `FileSensor` waits for a path. You implement `poke(context)` returning true or false, and configure `poke_interval`, `timeout`, and `mode`. The distinction matters operationally: a normal operator runs and finishes, while a sensor can sit for hours holding a worker slot, which is exactly why `mode='reschedule'` and deferrable sensors exist.
code
python · 29 linesfrom airflow import DAG
from airflow.operators.bash import BashOperator
from airflow.operators.python import PythonOperator
from airflow.providers.amazon.aws.sensors.s3 import S3KeySensor
import pendulum
def load(**context):
return context["data_interval_start"].isoformat()
with DAG(
dag_id="daily_load",
start_date=pendulum.datetime(2026, 1, 1, tz="UTC"),
schedule="@daily",
catchup=False,
) as dag:
wait = S3KeySensor(
task_id="wait_for_drop",
bucket_name="raw",
bucket_key="events/{{ ds }}/_SUCCESS",
poke_interval=300,
timeout=60 * 60 * 6,
mode="reschedule",
)
unpack = BashOperator(task_id="unpack", bash_command="./unpack.sh {{ ds }}")
ingest = PythonOperator(task_id="ingest", python_callable=load)
wait >> unpack >> ingestgo deeper
Be ready to name three built-in operators and one sensor, and to say plainly that a sensor is an operator that polls until a condition is true.
Explain the parse-time versus run-time split: the DAG file builds task objects, the worker calls execute(). Know that sensors implement poke() and that poke_interval and timeout govern the wait.
Show that you treat a waiting sensor as consumed capacity. Be able to say when you would use reschedule mode, a deferrable sensor, or redesign so nothing has to wait at all.
Own the convention: which integrations get provider operators, which get thin Python tasks over hooks, and how you stop a fleet of DAGs from filling worker capacity with sleeping sensors.
## Operator, task, task instance An Airflow DAG file is Python that *declares* work; it does not perform it. An **operator** is a class describing a unit of work. Writing `BashOperator(task_id="extract", bash_command="./extract.sh")` inside a DAG constructs an object and registers it with that DAG — nothing executes at that moment. The registered object is a **task**. When the scheduler creates a DAG run for a particular data interval, it creates a **task instance** for each task; only when that task instance is queued and picked up by a worker does the operator's `execute(context)` method actually run. That separation has a practical consequence interviewers like: DAG files are re-parsed by the scheduler over and over, so anything expensive written at module level — or inside an operator's `__init__` — runs on every parse rather than once per run. Real work belongs in `execute()`. ## The operator families - **Core/standard operators**: `BashOperator`, `PythonOperator`, `EmptyOperator` (the do-nothing placeholder, called `DummyOperator` before Airflow 2.4), `BranchPythonOperator`, `ShortCircuitOperator`. - **Provider operators**: shipped in separately versioned packages such as `apache-airflow-providers-amazon`, `apache-airflow-providers-google`, `apache-airflow-providers-cncf-kubernetes`. Examples: `BigQueryInsertJobOperator`, `KubernetesPodOperator`, `GlueJobOperator`. - **Transfer operators**: move data between two systems, e.g. `GCSToBigQueryOperator`. Underneath most provider operators sits a **hook** — the reusable client wrapper (`S3Hook`, `PostgresHook`) that knows how to turn an Airflow **Connection** id into an authenticated client. When no operator fits, calling a hook from your own Python task is the sanctioned path. Operators also declare `template_fields`: a list of constructor arguments that Airflow renders through Jinja at run time, which is how `{{ data_interval_start }}` or `{{ ds }}` ends up inside a `bash_command`, an S3 key or a SQL string. ## Sensors A sensor is a specialised operator whose whole purpose is waiting. It subclasses `BaseSensorOperator` and implements `poke(context)`, which returns `True` when the condition is satisfied and `False` otherwise. Airflow keeps calling `poke()` until it returns true or the sensor gives up. The knobs you will be asked about: - `poke_interval` — seconds between checks. - `timeout` — how long the sensor may keep waiting before failing. - `mode` — `'poke'` (hold the worker slot for the whole wait) or `'reschedule'` (end the task after each failed check and re-queue it, freeing the slot). - `soft_fail` — on timeout, mark the sensor *skipped* rather than *failed*, which lets an optional path be short-circuited without paging anyone. - `exponential_backoff` — lengthen the gap between pokes over time. Common examples: `S3KeySensor`, `GCSObjectExistenceSensor`, `FileSensor`, `SqlSensor`, `DateTimeSensor`, `ExternalTaskSensor`. Recent Airflow also lets you write one inline with the `@task.sensor` decorator instead of subclassing. ## Why the split is an operations question, not a vocabulary question An operator that runs a query for four minutes occupies a slot for four minutes and that is fine. A sensor waiting six hours in poke mode occupies a slot for six hours doing nothing. Fill your worker capacity with sleeping sensors and nothing else in the deployment can start — a classic self-inflicted outage. The three answers are: `mode='reschedule'` for long waits, deferrable sensors (`deferrable=True`) which hand the wait to the async triggerer process, and, where possible, not waiting at all by letting the producing pipeline trigger the consumer. ## TaskFlow Modern DAGs often skip explicit `PythonOperator` construction and use the `@task` decorator: the decorated function becomes a task, and its return value is passed downstream automatically. It is still an operator underneath — the decorator is authoring sugar, not a different execution model. Mixing decorated tasks with classic operator objects in one DAG is normal and expected. ## What interviewers listen for That you say a sensor **is** an operator rather than a parallel concept; that you know declaring a task is not running it; that you can name a couple of provider operators without inventing them; and that you immediately connect sensors to slot consumption rather than treating waiting as free.
- What is the difference between a task and a task instance in Airflow?A task is the operator object declared in the DAG — it exists once, in the DAG definition. A task instance is one execution of that task for one DAG run, with its own state, try number, logs and duration. Retries create new tries of the same task instance, not new tasks.
- Where do operators like KubernetesPodOperator actually come from?From provider packages installed alongside Airflow, such as apache-airflow-providers-cncf-kubernetes or apache-airflow-providers-amazon. They version independently of Airflow core, which means an operator can gain features or change arguments without an Airflow upgrade — and that provider pins belong in your image build.
- How does the TaskFlow @task decorator relate to PythonOperator?@task wraps a plain Python function into a Python-based task, so it is the same execution model with less boilerplate. The difference visible to you is data passing: the function's return value is pushed to XCom and passed to downstream decorated tasks automatically instead of being wired by hand.
saying these in an interview costs you the question
- Says a sensor is a separate concept, not an operator subclass
- Thinks instantiating an operator in a DAG file runs it immediately
- Believes a waiting sensor is free because it does no work
- Confuses KubernetesPodOperator with the KubernetesExecutor
- Claims every external system needs a hand-written custom operator