No while loops
Use for i in range(n) instead of while. Starlark forbids while to prevent infinite loops.
Performance tips, memory optimization, and coding patterns for writing efficient Starlark code.
Starlark uses garbage collection, but GC only runs between top-level statements—not inside function calls. This means memory can accumulate during execution.
# Bad: allocates memory on every iterationfor target in huge_target_list: result = memory_intensive_function(x, y) process(target, result)
# Good: hoist allocation outside the loopresult = memory_intensive_function(x, y)for target in huge_target_list: process(target, result)# Bad: stores all targets in memorytargets = generate_all_targets(n)for target in targets: process(target)
# Good: process as you generatefor i in range(n): target = generate_target(i) process(target)These methods allocate a new list on every call:
# Bad: allocates a new list each iteration if called in loopfor key, value in config.items(): # Allocates! process(key, value)
# Good: cache the items if iterating multiple timesitems = config.items()for key, value in items: process(key, value)# Bad: creates a billion-element listmillion = [1 for i in range(1 << 20)]billion = million * (1 << 10) # 1GB+ memory!
# Good: process in chunks or use generatorsdef process_range(start, end): for i in range(start, end): process(i)# Bad: processes all items even when one failsdef validate_all(items): errors = [] for item in items: if not is_valid(item): errors.append(item) if errors: fail("Invalid items: " + str(errors))
# Good: fail fastdef validate_all(items): for item in items: if not is_valid(item): fail("Invalid item: " + str(item))# Bad: O(n) lookupdef find_by_name(items, name): for item in items: if item.name == name: return item return None
# Good: O(1) lookup with dictitems_by_name = {item.name: item for item in items}def find_by_name(name): return items_by_name.get(name)# Bad: recomputes on every calldef get_config(): return expensive_computation()
for target in targets: cfg = get_config() # Called N times! process(target, cfg)
# Good: compute onceconfig = expensive_computation()for target in targets: process(target, config)Build files (BUCK, BUILD.bazel) should be declarative:
# Good: simple, declarativepython_library( name = "mylib", srcs = glob(["*.py"]), deps = [":utils"],)
# Avoid: complex logic in build files# for x in some_list:# if condition(x):# python_library(...)Complex logic belongs in separate .bzl files:
def my_python_library(name, srcs, deps = []): """Wrapper with team defaults.""" python_library( name = name, srcs = srcs, deps = deps + ["//common:base"], visibility = ["//..."], )# BUCK or BUILD.bazelload("//tools:macros.bzl", "my_python_library")
my_python_library( name = "mylib", srcs = glob(["*.py"]),)# Baddef f(x, y): return x + y
# Gooddef calculate_total_size(file_sizes, overhead_bytes): return file_sizes + overhead_bytesdef process_targets(targets): print("Processing {} targets".format(len(targets))) for i, target in enumerate(targets): if i % 100 == 0: print("Progress: {}/{}".format(i, len(targets))) process(target)def create_library(name, srcs, deps = None): # Initialize mutable default if deps == None: deps = []
# Validate early if not name: fail("name is required") if not srcs: fail("srcs cannot be empty") if type(deps) != "list": fail("deps must be a list, got: " + type(deps))
# Proceed with valid inputs native.library(name = name, srcs = srcs, deps = deps)Build systems like Buck2 offer profiling to identify bottlenecks:
# Profile loading phasebuck2 profile loading --mode=heap-summary-allocated -o profile.csv //path:target
# Profile analysis phasebuck2 profile analysis --mode=heap-summary-allocated -o profile.csv //path:target| Mode | Description |
|---|---|
heap-summary-allocated | Time and allocations per function |
heap-summary-retained | Memory retained after freezing |
time-flame | Flamegraph of time spent |
statement | Time per statement |
No while loops
Use for i in range(n) instead of while. Starlark forbids while to prevent infinite loops.
No recursion
Recursion is disabled in standard Starlark. Use iteration instead.
Frozen values
Values become immutable after a module loads. Don’t rely on mutation across files.
No classes
Use struct() or record() instead of classes. Starlark has no OOP.