What does the function control_dependencies do?
Master System Design with Codemia
Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.
Introduction
In TensorFlow 1.x, tf.control_dependencies() is a context manager that forces operations defined inside it to execute only after specified dependency operations have completed. TensorFlow's dataflow graph normally executes operations in any order that satisfies data dependencies, but some operations — like variable assignments, metric updates, and logging — have no data dependency on each other yet must execute in a specific order. control_dependencies adds explicit execution ordering constraints to the graph without creating data flow edges.
Basic Usage
Operations created inside the with tf.control_dependencies([ops]) block will not execute until all operations in the dependency list have finished. The dependency is on execution, not on the return value.
Ensuring Variable Updates Before Reading
tf.identity() is a common pattern — it creates a new operation (a copy) inside the control dependency block, ensuring the dependency is enforced. Without it, there is no new operation to attach the dependency to.
Training Loop: Apply Gradients After Update Ops
This is the most common real-world use of control_dependencies. Batch normalization update operations must run each training step, but they have no data dependency on the optimizer. Without control_dependencies, the moving mean and variance would never update.
Multiple Dependencies
You can pass multiple operations to the dependency list. All of them must complete before any operation inside the block executes.
TF2 Equivalent: Eager Execution
TF2's eager execution mode runs operations sequentially, making control_dependencies unnecessary for most code. Inside @tf.function, TensorFlow traces a graph but handles common patterns like batch normalization updates automatically through Keras.
Nesting Control Dependencies
Nested control_dependencies blocks create a chain: op_a must finish before op_b, and op_b must finish before op_c.
Common Pitfalls
- Not creating a new op inside the block:
control_dependenciesonly applies to operations created inside thewithblock. If you reference an existing tensor without creating a new operation, the dependency is not enforced. Usetf.identity(tensor)to create a new op that carries the dependency. - Assuming control_dependencies creates data flow: Control dependencies only enforce execution order. They do not pass data between operations. If operation B reads from a variable that operation A writes, you still need a control dependency to guarantee A completes before B reads.
- Using control_dependencies in TF2 eager mode: In eager mode, operations execute immediately in Python order, so
control_dependencieshas no effect. It only works in graph mode (tf.compat.v1or inside@tf.functionwith explicit graph construction). - Forgetting batch norm update ops: The most common bug is training a model with batch normalization without using
control_dependencies(TF1) ormodel(x, training=True)(TF2) to ensure moving statistics are updated each step. The model trains but performs poorly at inference. - Overusing control_dependencies and reducing parallelism: Adding unnecessary dependencies forces sequential execution of operations that could otherwise run in parallel on GPU. Only add dependencies where execution order genuinely matters.
Summary
tf.control_dependencies([ops])forces operations inside the block to wait for the listed ops to complete- Most common use: ensuring batch normalization update ops run during training
- Use
tf.identity()inside the block to create a new operation that carries the dependency - In TF2 eager mode, operations run sequentially by default —
control_dependenciesis not needed - Inside
@tf.function, Keras handles update ops automatically - Avoid overuse — unnecessary dependencies reduce GPU parallelism

