skip to content

Scripting Language Fundamentals

The language itself: variables and types, arrays and hashtables, control flow, and advanced functions with param blocks and CmdletBinding. Interviewers ask about ValueFromPipeline and the begin/process/end blocks, because that is what makes your function behave like a real cmdlet.

part ofCommand-line shellsoverview, primer and where to startread it →
on this pageshow

questions

6

In PowerShell, what do the `@()` and `@{}` literals each create, and how do you add an item to each afterwards?

level: juniorimportance: must knowfreq 68%

answer

  1. parentheses versus braces after the sigil
  2. positional list versus keyed lookup
  3. one of them is fixed size
  4. the plus-equals is a copy, not an append
  5. [ordered] when key order matters

basics

~20 s

@() builds an array — an ordered list of values indexed by position. @{} builds a hashtable — unordered key/value pairs. Append to an array with $a += $item; add or update a hashtable entry with $h['key'] = $value.

solid answer

~40 s

`@(...)` is the array subexpression operator: `@('web01','web02')` gives a `System.Object[]` you index by position (`$a[0]`) and count with `$a.Count`; `@()` alone is an empty array. `@{...}` is a hashtable literal: `@{ Name = 'db01'; Port = 5432 }` gives a `System.Collections.Hashtable` you read as `$h['Port']` or `$h.Port`. Adding differs in kind. A PowerShell array is a fixed-size .NET array, so `$a += 'web03'` does not append in place — it allocates a new array, copies the old elements in, and rebinds the variable. A hashtable is genuinely mutable: `$h['Ssl'] = $true` adds a key, and assigning to an existing key overwrites it. If you need insertion order preserved, use `[ordered]@{ ... }`, which produces an `OrderedDictionary` instead of a hashtable.

code

powershell · 8 lines
powershell
$servers = @('web01', 'web02')
$servers += 'web03'                 # allocates a new array and copies

$config = @{ Name = 'db01'; Port = 5432 }
$config['Ssl'] = $true              # adds a key in place
$config.Port = 5433                 # overwrites an existing value

'{0} servers, {1} config keys' -f $servers.Count, $config.Count

go deeper

for a junior

Be able to write both literals from memory, index an array with $a[0], read a hashtable with $h['key'] or $h.Key, and say plainly that += on an array rebuilds it rather than appending.

for a middle

Explain that a PowerShell array is a fixed-size .NET array while a hashtable is mutable, and know [ordered]@{} and the [pscustomobject]@{} cast for producing structured output.

for a senior

Show the production habits: wrap command output in @() so zero and one results behave like many, and reach for a generic List when a collection grows inside a loop.

for a principal

Frame the choice as a contract question — code that emits objects composes with everything downstream, while code that emits hashtables or formatted text forces every consumer to re-parse it.

## Two literals, two different data structures PowerShell has one operator for making a list of values and another for making a lookup table, and they look confusingly similar — parentheses versus braces after the `@` sigil. `@( ... )` is the **array subexpression operator**. It evaluates whatever is inside and guarantees the result is an array: ```powershell $servers = @('web01', 'web02', 'web03') $servers[0] # web01 - zero-based index $servers[-1] # web03 - negative index counts from the end $servers.Count # 3 $empty = @() # an empty array ``` You do not strictly need `@()` to make an array — `$a = 1, 2, 3` works too, because the comma is the array operator. The value of `@()` is that it *forces* an array even when the expression produces zero or one object. `@(Get-ChildItem -Filter 'nothing*')` is an empty array rather than `$null`, and a single result becomes a one-element array rather than a bare object. That is the standard defence against "my `.Count` blew up because the result was a scalar". `@{ ... }` is the **hashtable literal**. Entries are `Key = Value`, separated by semicolons on one line or by newlines across several: ```powershell $config = @{ Name = 'db01' Port = 5432 } $config['Port'] # 5432 $config.Port # 5432 - property-style access works too $config.Keys # Name, Port (order not guaranteed) ``` The result is a `System.Collections.Hashtable`. Keys are unique and lookup is by key, not by position — there is no `$config[0]` unless you literally used `0` as a key. ## Adding to each This is where the two diverge, and it is the part interviewers actually probe. A hashtable is mutable in place. `$config['Ssl'] = $true` adds the key if it is absent and overwrites the value if it is present. `$config.Remove('Port')` deletes one. `$config.Add('Ssl', $true)` also works but throws if the key already exists, which is why index assignment is the common idiom. An array is **fixed size**. PowerShell arrays are ordinary .NET arrays, and .NET arrays cannot grow. So `$servers += 'web04'` is not an append: PowerShell creates a brand-new array one element longer, copies every existing element into it, adds the new one, and points `$servers` at the result. For a handful of items that is invisible. In a loop over tens of thousands of items it becomes a real performance problem, and the fix is a growable collection such as `[System.Collections.Generic.List[string]]::new()` with `.Add()`. ## Ordered hashtables Hashtables make no promise about key order — the order you see when you print one is an implementation detail, not the order you typed. When order matters (building an object whose properties should appear in a fixed sequence, or emitting configuration a human will read), cast the literal: ```powershell $ordered = [ordered]@{ Name = 'db01'; Port = 5432 } ``` That produces a `System.Collections.Specialized.OrderedDictionary`, which keeps insertion order and additionally supports index access by position. ## Where hashtables show up beyond storage A hashtable is not just a container in PowerShell; it is a language mechanism. Two common uses worth knowing: - **Building objects.** `[pscustomobject]@{ Name = 'db01'; Port = 5432 }` turns a hashtable into a real object with `Name` and `Port` properties — the standard way to emit structured output from your own code. Cast from `[ordered]` if you care about property order. - **Splatting.** If a hashtable's keys match a command's parameter names, `Get-Thing @params` passes them all as named arguments. The sigil changes from `$` to `@` at the call site. This is how long parameter lists stay readable, and it is why hashtables turn up in scripts that never needed a lookup table. ## The common confusions Writing `@{ 'a', 'b' }` is a syntax error, not an array — braces mean key/value pairs. Writing `@('a' = 1)` is equally wrong. And `$hash.Count` counts keys while `$array.Count` counts elements, so a `Count` of 2 tells you nothing about which structure you are holding; check with `$x.GetType().Name` when in doubt.

  • Why do experienced scripters wrap a command in `@(...)` when assigning its output?
    Because a command that returns nothing yields `$null` and one that returns a single object yields a bare object, not a collection. `@(...)` forces both into an array, so `.Count` and `foreach` behave the same whether the result has zero, one, or many items. Without it, code that works on multi-result days breaks on a one-result day.
  • What is splatting, and why would you use a hashtable for it?
    Splatting passes a collection of arguments to a command as a unit. Build a hashtable whose keys are parameter names and values are the arguments, then call the command with `@` instead of `$`: `Get-Thing @params`. It keeps long parameter lists readable, lets you build arguments conditionally, and avoids backtick line continuations.
  • What is the difference between a hashtable and a PSCustomObject built from one?
    A hashtable is a keyed collection — you look values up by key and it has `Keys`, `Values`, and `Count`. `[pscustomobject]@{...}` produces an object whose keys became real properties, so it formats as a table, sorts and groups by property name, and exports cleanly to CSV or JSON. Emit objects from functions; keep hashtables for lookup and splatting.

saying these in an interview costs you the question

  • Claims @() and @{} both make lists, just different syntax
  • Thinks += appends to an array in place
  • Expects hashtable keys to come back in insertion order
  • Tries to index a hashtable by position, like $h[0]
  • Says .Count tells you whether it is an array or a hashtable

context

open as a page

What does adding `[CmdletBinding()]` above the param block of a PowerShell function change about how that function behaves?

level: middleimportance: must knowfreq 60%

basics

~20 s

[CmdletBinding()] turns a simple PowerShell function into an advanced function: it inherits the common parameters (-Verbose, -Debug, -ErrorAction, -ErrorVariable, -OutVariable and friends), gains access to $PSCmdlet, and binds arguments strictly instead of dumping extras into $args.

open as a page

A PowerShell function's last line is `return $result`, yet the caller receives several extra objects as well. Why does a PowerShell function emit more than the value named by `return`, and how do you stop it?

level: middleimportance: should knowfreq 50%

basics

~20 s

In PowerShell every expression whose value is not captured, assigned or redirected goes to the function's output stream. return only exits the scope and adds its argument to what was already emitted. Discard unwanted values with $null = expression, void, or | Out-Null.

open as a page

A PowerShell advanced function declares `[Parameter(ValueFromPipeline = $true)]` on `$Name` and puts all of its logic directly in the function body with no begin/process/end blocks. Piping ten names into it produces output for only the last one. Why?

level: middleimportance: should knowfreq 55%

basics

~20 s

A function body with no named blocks is treated entirely as the end block, which runs once after the pipeline is exhausted. The pipeline parameter is rebound for each incoming object, so by the time that code runs it holds only the last one. Per-item logic belongs in a process block.

open as a page

A PowerShell script builds a result set with `$results += $item` inside a loop over 100,000 records and slows to a crawl while memory climbs. What is happening, and what should it use instead?

level: seniorimportance: should knowfreq 38%

basics

~20 s

PowerShell arrays are fixed-size .NET arrays, so each += allocates a new array one element longer and copies every existing element — quadratic work plus heavy garbage. Use a List[T] with .Add(), or let the loop stream its output and assign the whole loop once.

open as a page

Your team maintains a large shared PowerShell codebase. Would you mandate `Set-StrictMode` together with typed, validated parameters across it, and what does that discipline cost?

level: principalimportance: nice to knowfreq 24%

basics

~20 s

Usually yes for shared code: PowerShell is permissive by default, so typos in variable and property names return $null silently and fail far from the cause. Strict mode and validation attributes convert those into immediate errors, at the cost of rejecting some dynamic idioms and of version-sensitive behaviour.

open as a page